离线压缩所有长度,用树状数组维护库存并按排名寻找最近的前驱和后继。
OJ: luogu
题目 ID: P5250
难度:普及+/提高
标签:树状数组离散化前驱后继python
日期: 2026-07-16 18:26
题意
维护一个长度互不相同的木材集合。支持插入;出货时删除等于需求长度的木材,否则删除距离需求最近的木材,距离相同选较短者。还要处理重复插入和空仓库。
思路
Python 标准库没有直接提供有序集合。普通有序列表配合 bisect 虽能找到位置,但中间插入、删除需要移动大量元素,最坏会达到
所有操作在开始时已经给出,可以先离线读完,把出现过的长度排序去重并映射到 1..k。树状数组的第 i 位表示该长度当前是否在仓库:
- 插入、删除是单点加
1或-1; - 前缀和表示某个长度之前有多少根现存木材;
kth(rank)找库存中第rank小的长度。
出货长度 x 不存在时,设严格小于 x 的库存数量为 left_count。第 left_count 小的是前驱,第 left_count+1 小的是后继。比较两边距离,使用 x-left <= right-x 保证距离相同时选较短的前驱。
Python 知识
- 集合推导式
{length for _, length in operations}完成长度去重。 enumerate(lengths,1)同时得到长度及其从1开始的树状数组下标。set负责期望存在性判断,树状数组负责顺序和排名;两个容器各做自己擅长的事。 None表示某一侧不存在候选,比设置超大哨兵更直观。/home/rainboy/mycode/hugo-blog/content/program_language/python/sorting_and_ordering.md:排序、离散化与有序查询。/home/rainboy/mycode/hugo-blog/content/program_language/python/collections_toolkit.md:集合的成员判断。
代码
python
import sys
class Fenwick:
def __init__(self, n):
self.n = n
self.tree = [0] * (n + 1)
def add(self, pos, delta):
while pos <= self.n:
self.tree[pos] += delta
pos += pos & -pos
def prefix_sum(self, pos):
total = 0
while pos:
total += self.tree[pos]
pos -= pos & -pos
return total
def kth(self, rank):
pos = 0
step = 1 << (self.n.bit_length() - 1)
while step:
nxt = pos + step
if nxt <= self.n and self.tree[nxt] < rank:
pos = nxt
rank -= self.tree[nxt]
step >>= 1
return pos + 1
def main():
data = list(map(int, sys.stdin.buffer.read().split()))
operations = list(zip(data[1::2], data[2::2]))
lengths = sorted({length for _, length in operations})
index = {length: i + 1 for i, length in enumerate(lengths)}
bit = Fenwick(len(lengths))
stock = set()
answer = []
for operation, length in operations:
pos = index[length]
if operation == 1:
if length in stock:
answer.append("Already Exist")
else:
stock.add(length)
bit.add(pos, 1)
continue
if not stock:
answer.append("Empty")
continue
if length in stock:
chosen = length
else:
left_count = bit.prefix_sum(pos - 1)
total = len(stock)
left = lengths[bit.kth(left_count) - 1] if left_count else None
right = lengths[bit.kth(left_count + 1) - 1] if left_count < total else None
if right is None or left is not None and length - left <= right - length:
chosen = left
else:
chosen = right
answer.append(str(chosen))
stock.remove(chosen)
bit.add(index[chosen], -1)
print("\n".join(answer))
if __name__ == "__main__":
main()cpp
/**
* P5250 【深基17.例5】木材仓库
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-27 00:00
* update_at: 2026-07-27 00:00
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int INF = 2147483647;
// BST 结点
struct Node {
int val, l, r, cnt, sz;
} tree[MAXN];
int root, idx;
int new_node(int val) {
++idx;
tree[idx].val = val;
tree[idx].l = tree[idx].r = 0;
tree[idx].cnt = tree[idx].sz = 1;
return idx;
}
void insert(int &u, int val) {
if (u == 0) { u = new_node(val); return; }
++tree[u].sz;
if (val == tree[u].val) { ++tree[u].cnt; return; }
if (val < tree[u].val) insert(tree[u].l, val);
else insert(tree[u].r, val);
}
// 删除一个值(只删一个)
void erase(int &u, int val) {
if (u == 0) return;
if (val == tree[u].val) {
if (tree[u].cnt > 1) { --tree[u].cnt; --tree[u].sz; return; }
if (!tree[u].l || !tree[u].r) { u = tree[u].l + tree[u].r; return; }
// 左右子树都存在:找前驱替换
int v = tree[u].l;
while (tree[v].r) v = tree[v].r;
tree[u].val = tree[v].val;
tree[u].cnt = tree[v].cnt;
tree[v].cnt = 1;
erase(tree[u].l, tree[v].val);
tree[u].sz = tree[tree[u].l].sz + tree[tree[u].r].sz + tree[u].cnt;
return;
}
if (val < tree[u].val) erase(tree[u].l, val);
else erase(tree[u].r, val);
tree[u].sz = tree[tree[u].l].sz + tree[tree[u].r].sz + tree[u].cnt;
}
// 查询 val 的排名
int get_rank(int u, int val) {
if (u == 0) return 1;
if (val == tree[u].val) return tree[tree[u].l].sz + 1;
if (val < tree[u].val) return get_rank(tree[u].l, val);
return tree[tree[u].l].sz + tree[u].cnt + get_rank(tree[u].r, val);
}
// 查询第 k 小
int kth(int u, int k) {
if (u == 0) return 0;
int lsz = tree[tree[u].l].sz;
if (k <= lsz) return kth(tree[u].l, k);
if (k <= lsz + tree[u].cnt) return tree[u].val;
return kth(tree[u].r, k - lsz - tree[u].cnt);
}
// 前驱
int pre(int u, int val) {
if (u == 0) return -INF;
if (tree[u].val >= val) return pre(tree[u].l, val);
return max(tree[u].val, pre(tree[u].r, val));
}
// 后继
int nxt(int u, int val) {
if (u == 0) return INF;
if (tree[u].val <= val) return nxt(tree[u].r, val);
return min(tree[u].val, nxt(tree[u].l, val));
}
int main() {
int q;
scanf("%d", &q);
while (q--) {
int op, x;
scanf("%d%d", &op, &x);
if (op == 1) {
// 仓库中是否已有
int rk = get_rank(root, x);
if (kth(root, rk) == x && root) puts("Already Exist");
else insert(root, x);
} else {
if (root == 0) { puts("Empty"); continue; }
int rk = get_rank(root, x);
int smaller = kth(root, rk);
int target;
if (smaller == x) target = x;
else {
int big = kth(root, rk + 1);
if (rk <= 1) target = big;
else if (rk > tree[root].sz) target = smaller;
else target = (x - smaller <= big - x) ? smaller : big;
}
printf("%d\n", target);
erase(root, target);
}
}
return 0;
}复杂度
设操作数为 q。离散化需要
总结
需要动态前驱、后继时,不能只看到 bisect 查询快,还要计算列表修改成本。离线坐标压缩把大整数长度变成排名,再用树状数组维护哪些排名仍存在。