根号数据结构
根号数据结构
在以前,我十分讨厌带根号的数据结构,认为不够优雅.
但是在很多时候,带 数据结构的作用比较局限,且复杂.
这个时候,根号数据结构的作用是十分巨大的.
根号数据结构主要依赖于复杂度的分析,即将看似暴力的做法捏合在一起.
普通莫队
最简单的莫队.
可以处理只有查询,没有修改的问题.
每次先按照左端点所在块排序,若块相同则按照右端点排序.
时间复杂度为 .
1 2 3 4 | 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; } |
没有什么其他强调的,但尽量不要在后面多加一个 , 否则复杂度就很难看.
带修改莫队
加入现在的题目要支持单点修改,区间查询,普通莫队就无能为力了.
这时就诞生了带修改莫队.
按照 所在块为第 关键字排序,然后询问时间为第 3 关键字.
每次先将左右指针按照普通莫队一样移动到询问区间,然后再移动修改操作的时间轴.
加入当前加入的修改操作对区间无影响,则直接忽略.
但是我们可能对后面的区间有影响(这个数只是当前不在询问区间里,但以后可能在)
所以无论有无影响都要执行修改操作(就是交换点值之类的)
排序代码:
1 2 3 4 5 6 7 8 | 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; } |
时间复杂度为 .
例题:CF940F
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | #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; } |
要注意使用带修改莫队的前提是单点更新的操作可以十分方便地进行撤回.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY