根号数据结构
根号数据结构
在以前,我十分讨厌带根号的数据结构,认为不够优雅.
但是在很多时候,带 $\mathrm{log}$ 数据结构的作用比较局限,且复杂.
这个时候,根号数据结构的作用是十分巨大的.
根号数据结构主要依赖于复杂度的分析,即将看似暴力的做法捏合在一起.
普通莫队
最简单的莫队.
可以处理只有查询,没有修改的问题.
每次先按照左端点所在块排序,若块相同则按照右端点排序.
时间复杂度为 $O(q \sqrt n)$.
bool cmp(query i, query j) { if(i.l / B == j.l / B) return i.r < j.r; return i.l / B < j.l / B; }
没有什么其他强调的,但尽量不要在后面多加一个 $\log$, 否则复杂度就很难看.
带修改莫队
加入现在的题目要支持单点修改,区间查询,普通莫队就无能为力了.
这时就诞生了带修改莫队.
按照 $\mathrm{l,r}$ 所在块为第 $1,2$ 关键字排序,然后询问时间为第 3 关键字.
每次先将左右指针按照普通莫队一样移动到询问区间,然后再移动修改操作的时间轴.
加入当前加入的修改操作对区间无影响,则直接忽略.
但是我们可能对后面的区间有影响(这个数只是当前不在询问区间里,但以后可能在)
所以无论有无影响都要执行修改操作(就是交换点值之类的)
排序代码:
bool cmp(query i, query j) { if(i.l / B == j.l / B) { if(i.r / B == j.r / B) return i.id < j.id; else return i.r / B < j.r / B; } else return i.l / B < j.l / B; }
时间复杂度为 $\mathrm{ O(m \times n^{\frac{2}{3}} )}$.
例题:CF940F
#include <cstdio> #include <cstring> #include <vector> #include <cmath> #include <algorithm> #define N 200009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; int B; int n,Q,val[N],A[N], ans[N], cnt[N], ty[N], num[N], cc, mo, qu ; struct Data { int pos, v, t; // 将 pos 改为 v. // 更改时刻为 t. }a[N]; struct query { int l,r,id, t; // 查询时刻为 id. }q[N]; bool cmp(query i, query j) { if(i.l / B == j.l / B) { if(i.r / B == j.r / B) return i.id < j.id; else return i.r / B < j.r / B; } else return i.l / B < j.l / B; } void add(int v) { cnt[num[v]] -- ; num[v] ++ ; cnt[num[v]] ++ ; } void del(int v) { cnt[num[v]] --; // num[v]: v 的数量. num[v] --; cnt[num[v]] ++; } void modify(int x, int id) { if(a[x].pos >= q[id].l && a[x].pos <= q[id].r) { del(val[a[x].pos]); // 将原来这个位置的东西删掉. // val[a[x].pos] = a[x].v; // 修改成新元素. add(a[x].v); } swap(a[x].v, val[a[x].pos]); } int main() { // setIO("input"); scanf("%d%d",&n,&Q); B = pow(n, (double)2/3); for(int i=1;i<=n;++i) { scanf("%d",&val[i]); A[++cc] = val[i]; } for(int i=1;i<=Q;++i) { int op,x,y; scanf("%d",&op); // ty[i] = op; if(op==1) { scanf("%d%d",&x,&y); ++qu; q[qu].id = i; q[qu].l = x; q[qu].r = y; q[qu].t = mo; } if(op==2) { scanf("%d%d",&x,&y); // 将 x -> y ++mo; a[mo].pos = x; a[mo].v = y; a[mo].t = mo; A[++cc] = y; } } sort(A+1, A+1+cc); for(int i=1;i<=n;++i) { val[i]=lower_bound(A+1,A+1+cc,val[i])-A; } for(int i=1;i<=mo;++i) { a[i].v = lower_bound(A+1,A+1+cc,a[i].v)-A; } // a 并不用排序. // q 需要排序. sort(q+1, q+1+qu, cmp); int l=1,r=0,cur=0; for(int i=1;i<=qu;++i) { while(r < q[i].r) add( val[++ r] ); while(l > q[i].l) add( val[-- l] ); while(r > q[i].r) del( val[r --] ); while(l < q[i].l) del( val[l ++] ); while(cur < q[i].t) { modify(++cur, i); } while(cur > q[i].t) { modify(cur--, i); // 撤销操作. } for(ans[q[i].id] = 1; cnt[ans[q[i].id]]; ++ ans[q[i].id]); } for(int i=1;i<=Q;++i) { if(ans[i]) { printf("%d\n", ans[i]); } } // finish ~ // Flourish // return 0; }
要注意使用带修改莫队的前提是单点更新的操作可以十分方便地进行撤回.