更多的莫队
带修改莫队
有这么一类问题,毒瘤数据结构题目(比如什么树套树套树),除了询问还有修改,然而支持离线,能快速地在区间首尾添加/删除元素。
普通莫队可以看做是将每个询问看成了一个二元组 (l, r) 。每次修改可以看成时间往后移动了一个单位。所以对于带修改莫队将每个询问看成三元组 (l, r, t) 、
如何确定较为高效的处理查询的顺序?
继续参考普通莫队,将前两维分块,按块编号排序,然后按时间为第三关键字升序排序。
在进行处理询问的过程中,还要记录一个当前时间,当处理到询问i,它的时间和当前时间不一样,就要通过撤销/执行某些操作来使时间一样。
那。。如何确定块大小呢?
现在假设块大小为$n^{\alpha}$,那么一共就有$n^{1-\alpha}$块。假设询问操作和序列长度同级,意思是它们的范围都是$n$。现在用当每次修改时间复杂度和每次移动指针的时间复杂度均为$O(1)$来举例子。
- 对于$l$指针的移动:对于每个询问,左指针至多移动$n^{\alpha}$次,所以对于左指针移动的总时间复杂度为$O(n^{1 + \alpha})$。
- 对于$r$指针的移动:同理上,可也以得到它移动的总时间复杂度为$O(n^{1+\alpha})$。
- 对于$t$指针的移动:考虑当$l$处于块$i$,$r$处于块$j$时,处理完满足左端点在块$i$内,右端点在块$j$内的时间复杂度,因为$t$是递增的,所以时间复杂度为$O(n)$,又因为总共有$O(n^{2 - 2\alpha})$个块对,所以时间指针的总时间复杂度为$O(n^{3-2\alpha})$。
为了使三个时间复杂度的最大值最小,所以令
$n^{3-\alpha} = n^{1 + \alpha}$
然后解得
$\alpha = \frac{2}{3}$
所以当$\alpha = \frac{2}{3}$,带修改莫队时间复杂度有最小值$O(n^{5/3})$。(决定TLE还是AC的三分之一)
然后是道模板题的代码[题目传送门]:
Code
1 /** 2 * luogu 3 * Problem#1903 4 * Accepted 5 * Time: 100ms 6 * Memory: 2531k 7 */ 8 #include <bits/stdc++.h> 9 using namespace std; 10 typedef bool boolean; 11 typedef pair<int, int> pii; 12 #define fi first 13 #define sc second 14 15 typedef class Query { 16 public: 17 int l; 18 int r; 19 int id; 20 int t; 21 22 Query(int l = 0, int r = 0, int id = 0, int t = 0):l(l), r(r), id(id), t(t) { } 23 }Query; 24 25 #define BucketSize 1000005 26 27 int n, m; 28 int cs = 460; 29 int co = 0, cq = 0; 30 Query *chs; 31 pii* opts; 32 int* ar; 33 int bucket[BucketSize]; 34 35 boolean operator < (const Query& a, const Query& b) { 36 if(a.l / cs != b.l / cs) return a.l / cs < b.l / cs; 37 if(a.r / cs != b.r / cs) return a.r / cs < b.r / cs; 38 return a.t < b.t; 39 } 40 41 inline void init() { 42 scanf("%d%d", &n, &m); 43 chs = new Query[(m + 1)]; 44 opts = new pii[(m + 1)]; 45 ar = new int[(n + 1)]; 46 47 for(int i = 1; i <= n; i++) 48 scanf("%d", ar + i); 49 50 char buf[5]; 51 for(int i = 1, x, y; i <= m; i++) { 52 scanf("%s%d%d", buf, &x, &y); 53 if(buf[0] == 'Q') { 54 cq++; 55 chs[cq] = Query(x, y, cq, co); 56 } else { 57 opts[++co] = pii(x, y); 58 } 59 } 60 } 61 62 int cur; 63 int mdzzl, mdzzr, mdzzt; 64 int* ans; 65 66 inline void update(int p, int sign) { 67 bucket[p] += sign; 68 if(bucket[p] == 1 && sign == 1) cur++; 69 if(bucket[p] == 0 && sign == -1) cur--; 70 } 71 72 pii *bt; 73 inline void update(pii& op, boolean back) { 74 if(op.fi >= mdzzl && op.fi <= mdzzr) { 75 update(ar[op.fi], -1); 76 update(op.sc, 1); 77 } 78 if(!back) 79 bt[mdzzt] = pii(op.fi, ar[op.fi]); 80 ar[op.fi] = op.sc; 81 } 82 83 inline void solve() { 84 ans = new int[(cq + 1)]; 85 bt = new pii[(n + 1)]; 86 sort(chs + 1, chs + cq + 1); 87 mdzzl = 1, mdzzr = 0; 88 for(int i = 1; i <= cq; i++) { 89 while(mdzzr < chs[i].r) update(ar[++mdzzr], 1); 90 while(mdzzr > chs[i].r) update(ar[mdzzr--], -1); 91 while(mdzzl < chs[i].l) update(ar[mdzzl++], -1); 92 while(mdzzl > chs[i].l) update(ar[--mdzzl], 1); 93 while(mdzzt < chs[i].t) update(opts[++mdzzt], false); 94 while(mdzzt > chs[i].t) update(bt[mdzzt--], true); 95 ans[chs[i].id] = cur; 96 } 97 for(int i = 1; i <= cq; i++) 98 printf("%d\n", ans[i]); 99 } 100 101 int main() { 102 init(); 103 solve(); 104 return 0; 105 }
回滚莫队
再来看一类问题,仍然是毒瘤数据结构题,虽然木有修改但是可以快速在区间首或者区间尾插入元素但是难以删除,答案也不复杂(有时可能就1两个数)。
既然难以删除就思考如何避免删除的过程。
有注意到普通莫队的左端点在一个块内到处乱跳,考虑如果从块尾不断添加元素到询问的左端点?
显然这样是可行的,处理完这个询问,直接将左端点还原到块尾。只不过这里不是通过一个一个地删除元素来还原,而是在移动左端点前,备份答案,处理完询问后还原一下维护的量然后直接还原答案。但是有一个问题:如果询问的左右端点在同一块?
直接暴力好了,反正时间复杂度不超过$O(\alpha\sqrt{n})$(此处的$\alpha$是每次插入的时间复杂度)
然后要注意一个问题就是莫队的主过程要考虑询问的边界问题。
然后是道模板题的代码[题目传送门]:
Code
1 /** 2 * bzoj 3 * Problem#4241 4 * Accepted 5 * Time: 15408ms 6 * Memory: 5208k 7 */ 8 #include <bits/stdc++.h> 9 #ifdef WIN32 10 #define Auto "%I64d" 11 #else 12 #define Auto "%lld" 13 #endif 14 using namespace std; 15 typedef bool boolean; 16 #define ll long long 17 18 const int cs = 350; 19 20 typedef class Query { 21 public: 22 int l, r, id; 23 24 Query(int l = 0, int r = 0, int id = 0):l(l), r(r), id(id) { } 25 26 boolean operator < (Query b) const { 27 if(l / cs != b.l / cs) return l / cs < b.l / cs; 28 return r < b.r; 29 } 30 }Query; 31 32 int n, m; 33 int *ar; 34 Query* qs; 35 int *nar, *val; 36 37 inline void init() { 38 scanf("%d%d", &n, &m); 39 ar = new int[(n + 1)]; 40 nar = new int[(n + 1)]; 41 val = new int[(n + 1)]; 42 qs = new Query[(m + 1)]; 43 44 for(int i = 1; i <= n; i++) 45 scanf("%d", ar + i); 46 for(int i = 1; i <= m; i++) 47 scanf("%d%d", &qs[i].l, &qs[i].r), qs[i].id = i; 48 } 49 50 inline void decrease() { 51 memcpy(val, ar, sizeof(int) * (n + 1)); 52 sort(val + 1, val + n + 1); 53 for(int i = 1; i <= n; i++) 54 nar[i] = lower_bound(val + 1, val + n + 1, ar[i]) - val; 55 } 56 57 ll* ans; 58 ll cans = 0, hans = 0; 59 ll *bucket; 60 61 inline void update(int p) { 62 if((bucket[nar[p]] += ar[p]) > cans) 63 cans = bucket[nar[p]]; 64 } 65 66 inline void solve() { 67 bucket = new ll[(n + 1)]; 68 ans = new ll[(m + 1)]; 69 memset(bucket, 0, sizeof(ll) * (n + 1)); 70 sort(qs + 1, qs + m + 1); 71 int mdzzl, mdzzr, bl, f = 1; 72 for(int id = 0; f <= m; id++) { 73 while(f <= m && qs[f].l / cs == id && qs[f].r / cs == id) { 74 mdzzl = qs[f].l, mdzzr = qs[f].l - 1, cans = 0; 75 while(mdzzr < qs[f].r) update(++mdzzr); 76 ans[qs[f].id] = cans; 77 for(int i = mdzzl; i <= mdzzr; i++) 78 bucket[nar[i]] = 0; 79 f++; 80 } 81 mdzzl = bl = (id + 1) * cs, mdzzr = mdzzl - 1, cans = 0; 82 while(f <= m && qs[f].l / cs == id) { 83 while(mdzzr < qs[f].r) update(++mdzzr); 84 hans = cans; 85 while(mdzzl > qs[f].l) update(--mdzzl); 86 ans[qs[f].id] = cans; 87 while(mdzzl < bl && mdzzl < n) bucket[nar[mdzzl]] -= ar[mdzzl], mdzzl++; 88 cans = hans; 89 f++; 90 } 91 for(int i = bl; i <= mdzzr; i++) 92 bucket[nar[i]] = 0; 93 } 94 for(int i = 1; i <= m; i++) 95 printf(Auto"\n", ans[i]); 96 } 97 98 int main() { 99 init(); 100 decrease(); 101 solve(); 102 return 0; 103 }
树上莫队
求出树的括号序列,然后每个树上路径的问题就转化成括号序列上的区间问题。
(可能有细节没有想清楚,还有一种神奇的方法,定义了个什么对称差,然后推推式子可以得到某个东西)