Loading

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);
    }
}
posted @ 2020-10-30 14:46  MQFLLY  阅读(126)  评论(0编辑  收藏  举报