题意
动态区间第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 imes 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 ", b[query(1, nn, K[i])]); } } return 0; }
显然因为历史版本是自己所以此题维护权值线段树即可。
可如果要求查看历史版本(比如回到某一次修改前)就必须要用主席树了。