自动排行(树状数组套主席树)

题意

动态区间第k小。写一个数据结构,支持单点修改,查询区间第k小。

Solution

我们回想一下静态第k小是怎么做的,其实就是查询前缀和。

而这道题如果还按之前的写法,修改的时间复杂度是$O(N)$的,显然不优。

这时我们就要换一种思路,那就是用树状数组维护前缀和。

这时树状数组的每个节点维护的不是一个值了,而是一棵主席树(虽然普通的权值线段树也行)。

对于修改操作,和普通的树状数组一样找到所有要改的节点,让后再它们所代表的主席树上修改,历史版本就是这个节点上一次修改的值。如下:

void change(int x, int v) {
    int tmp = a[x];
    while (x <= n) {
        ins(rt[x], rt[x], 1, nn, tmp, v);
        x += lowbit(x);
    }
}

对于查询,我们首先要预处理出所有要选的主席树(类似树状数组的查询),之后在那些主席树上查询即可。

查询的思路同静态第k小。

时间复杂度:$O(N \times \log^2{N})$

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 4000010;
struct Chairman_Tree{
    int ls, rs;
    int val;
}tr[N * 30];
int rt[N], tot;
int n, m;
int a[N], b[N], nn;
int L[N], R[N], K[N];
char opt[N];
int top1, top2;
int sk1[N], sk2[N];
inline int lowbit(int x) {
    return x & -x;
}
void ins(int &cur, int pre, int l, int r, int pos, int v) {
    cur = ++tot, tr[cur] = tr[pre], tr[cur].val += v;
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (pos <= mid) ins(tr[cur].ls, tr[pre].ls, l, mid, pos, v);
    else ins(tr[cur].rs, tr[pre].rs, mid + 1, r, pos, v);
}
void change(int x, int v) {
    int tmp = a[x];
    while (x <= n) {
        ins(rt[x], rt[x], 1, nn, tmp, v);
        x += lowbit(x);
    }
}
void init(int x, int y) {
    top1 = top2 = 0;
    while (x) {
        sk1[++top1] = rt[x];
        x -= lowbit(x);
    }
    while (y) {
        sk2[++top2] = rt[y];
        y -= lowbit(y);
    }
}
int query(int l, int r, int k) {
    if (l == r) return l;
    int num = 0;
    for (int i = 1; i <= top1; i++) num -= tr[tr[sk1[i]].ls].val;
    for (int i = 1; i <= top2; i++) num += tr[tr[sk2[i]].ls].val;
    int mid = (l + r) >> 1;
    if (k <= num) {
        for (int i = 1; i <= top1; i++) sk1[i] = tr[sk1[i]].ls;
        for (int i = 1; i <= top2; i++) sk2[i] = tr[sk2[i]].ls;
        return query(l, mid, k);
    } else {
        for (int i = 1; i <= top1; i++) sk1[i] = tr[sk1[i]].rs;
        for (int i = 1; i <= top2; i++) sk2[i] = tr[sk2[i]].rs;
        return query(mid + 1, r, k - num);
    }
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        b[++nn] = a[i];
    }
    for (int i = 1; i <= m; i++) {
        cin >> opt[i];
        scanf("%d%d", &L[i], &R[i]);
        if (opt[i] == 'Q') {
            scanf("%d", &K[i]);
        } else {
            b[++nn] = R[i];
        }
    }
    sort(b + 1, b + nn + 1);
    nn = unique(b + 1, b + nn + 1) - b - 1;
    for (int i = 1; i <= n; i++) {
        int p = lower_bound(b + 1, b + nn + 1, a[i]) - b;
        a[i] = p;
    }
    for (int i = 1; i <= n; i++) {
        change(i, 1);
    }
    for (int i = 1; i <= m; i++) {
        if (opt[i] == 'C') {
            change(L[i], -1);
            a[L[i]] = lower_bound(b + 1, b + nn + 1, R[i]) - b;
            change(L[i], 1);
        } else {
            init(L[i] - 1, R[i]);
            printf("%d\n", b[query(1, nn, K[i])]);
        }
    }
    return 0;
}

显然因为历史版本是自己所以此题维护权值线段树即可。

可如果要求查看历史版本(比如回到某一次修改前)就必须要用主席树了。

猜你喜欢

转载自www.cnblogs.com/zcr-blog/p/12736888.html
今日推荐