P4587 [FJOI] 神秘数 可持久化线段树
P4587 [FJOI] 神秘数 可持久化线段树
题意
给定\(n\)个正整数\(a_i\),\(m\)个询问,每次询问给定一个区间\(l,r\),求\([l,r]\)内的数所构成的最小的不能可重子集的和表示的正整数
\[1\leq n,m\leq10000
\\
\sum a_i \leq 10^9
\]
分析
注意到的性质
- 对这一区间排序后,假设该区间能表示的正整数是\([1,pos]\),此时再加入一个数,考虑这个数对区间的影响
-
- 若\(x > pos + 1\),显然不会对这个区间产生影响,因为任意加都不会达到\(pos + 1\),不会影响答案
- 若\(x <= pos + 1\),显然会使得答案更新为\([1,pos +x]\)
- 因此我们此时需要的\(x\)范围就是\([mx + 1,pos + 1]\),记\(mx\)为此时答案中的上界,(显然我们不需要重复计算),因此每次更新都加入这些范围数的和,我们发现这是一个倍增的过程,因此复杂度是\(log\)级别的
现在需要解决的是维护区间信息中的区间值域和,这正好可以用主席树解决
学习:主席树动态开点
代码
const int N = 1e9;
int cnt;
struct Tree {
int l, r, sum;
};
Tree node[5000005];
int root[5000005];
void update(int& rt, int l, int r, int x) {
node[++cnt] = node[rt];
rt = cnt;
node[rt].sum += x;
if (l == r) return;
int mid = l + r >> 1;
if (mid >= x) update(node[rt].l, l, mid, x);
else update(node[rt].r, mid + 1, r, x);
}
int query(int i, int j, int L, int R, int l, int r) {
if (!(node[j].sum - node[i].sum) || r < L || R < l) return 0;
if (L >= l && R <= r) return node[j].sum - node[i].sum;
int mid = L + R >> 1;
return query(node[i].l, node[j].l, L, mid, l, r) + query(node[i].r, node[j].r, mid + 1, R, l, r);
}
int main() {
int n = readint();
for (int i = 1; i <= n; i++) {
root[i] = root[i - 1];
update(root[i], 1, N, readint());
}
int m = readint();
while (m--) {
int l = readint();
int r = readint();
int mx, pos;
mx = pos = 0;
for (int Sum;;) {
Sum = query(root[l - 1], root[r], 1, N, mx + 1, pos + 1);
if (!Sum) break;
mx = pos + 1;
pos += Sum;
}
printf("%d\n", pos + 1);
}
}