莫队学习笔记(1)普通莫队
莫队算法(Mo's Algorithm,Sweepline Mo)实质上是暴力的优化,在离线情况下利用分块思想和增量思想来处理区间询问问题。
考虑一个非常简单的问题:给定长为 $n$ 的序列,$q$ 次询问,每次询问 $[l,r]$ 的和。
虽然这个问题有很多更优秀的做法(如前缀和与差分等)处理,但是假设询问的不是区间和而是一些非常复杂的信息导致这些做法都无能为力时,我们就需要考虑如何优化严格 $O(nq)$ 的直接暴力。
考虑对暴力进行一个小小的优化,若我们维护当前询问区间的答案,处理下一个询问时将左右端点移动到该询问区间的位置,在这一过程中不断在左右端点处插入或删除一个位置来更新答案,即利用增量思想通过左右端点的移动来更新答案。
这样的话并不是严格的 $O(nq)$,若相邻两个询问区间的左端点之间的距离和右端点之间的距离都很小时非常优秀,但如果很大仍然会被卡成 $O(nq)$,因为我们的左右端点可能会移动很远才能到达下一个区间的位置,那么我们能否优化一下来减少移动距离呢?
考虑将所有询问离线下来并分块,以左端点所在的块的编号为第一关键字升序排序,以右端点所在的位置为第二关键字升序排序。
这样复杂度会优化到什么程度呢?不妨设块长为 $B$,则一共有 $\lceil\dfrac{n}{B}\rceil$ 个块。
对于块内,虽然左端点是乱序的但是右端点是单调不降的,所以左端点的移动为 $O(Bq)$,右端点的移动为 $O(\dfrac{n}{B}n)=O(\dfrac{n^2}{B})$。
对于块间,由于只有 $\lceil\dfrac{n}{B}\rceil$ 个块,所以左端点的移动为 $O(\dfrac{n}{B}B)=O(n)$,右端点的移动为 $O(\dfrac{n}{B}n)=O(\dfrac{n^2}{B})$。
因此时间复杂度为 $O(Bq+\dfrac{n^2}{B})$,均值一下取 $B=\lceil\dfrac{n}{\sqrt{q}}\rceil$ 时最优,时间复杂度为 $O(n\sqrt{q})$,就变成了根号科技!
虽然莫队思维和代码难度都很低,但毕竟是根号科技,若正解是 $\log$ 科技并需要离线则出题人往往会卡莫队,所以有一些卡常技巧:
第一个卡常技巧是预处理出每个位置所在的块的编号,而不是在比较函数里面现求每个询问左端点所在的块的编号,这样能够大幅度减少除法运算次数,在 $q$ 比较大的时候(例如 $q=2\times10^6$)效果非常显著。
第二个卡常技巧是奇偶化排序,不妨来看下面这个例子(假设块的大小为 $2$,有 $4$ 个询问)。
1 1 2 100000 3 1 4 100000
当第一个块处理完后左端点 $l=2$,右端点 $r=100000$,此时我们显然可以发现如果 $l$ 移到 $4$ 就可以顺便处理第 $4$ 个询问。
但是由于 $r$ 在每个块内都是从小到大排序的,所以是直接将 $r$ 移回 $1$ 处理第 $3$ 个询问,再将 $r$ 移到 $100000$ 处理第 $4$ 个询问。
因此考虑奇偶化排序来优化,奇数块的右端点都从小到大排序,偶数块的右端点都从大到小排序,这样处理完了奇数块 $r$ 就可以直接往左移动顺便处理偶数块,处理完了偶数块 $r$ 就可以直接往右移动顺便处理奇数块,这样能够减少不少 $r$ 不必要的移动。
第三个卡常技巧是块长带一个常数在随机数据下非常优秀,可以设常数为 $\sqrt{3}$ 即 $B=\lceil\sqrt{3}\dfrac{n}{\sqrt{q}}\rceil$。
第四个卡常技巧是使用 fread 和 fwrite,因为出题人卡莫队时往往 $n,q$ 会开到百万级别,非常快的读入和输出很有必要。
此外,由于莫队的修改操作总花费是 $O(n\sqrt{q})$ 而查询操作总花费却是 $O(q)$,所以当修改操作无法 $O(1)$ 时最好不要使用修改和查询操作复杂度相同的数据结构,而是使用查询操作复杂度高但修改操作复杂度低的数据结构,利用平衡思想优化复杂度,例如:修改和查询操作都为 $O(\log{n})$ 改为修改操作 $O(1)$ 查询操作 $O(\sqrt{n})$ 可以将复杂度从 $O(n\sqrt{q}\log{n})$ 优化到 $O(n\sqrt{q}+q\sqrt{n})$。
- 例题 1:P1972 [SDOI2009]HH的项链(双倍经验:SP3267 DQUERY - D-query,但它不卡莫队)
题意:给定长为 $n$ 的序列和 $m$ 次询问,每次询问区间颜色个数,$1\le n,m\le10^6$,时限 2s。
模板题,设 $cnt[i]$ 表示颜色 $i$ 的出现次数,利用 $cnt[i]$ 来 $O(1)$ 更新答案即可。
很无奈莫队卡不过去(提交记录:R69733012 记录详情),双倍经验题 $n\le3\times10^4,\ m\le2\times10^5$ 能够轻松跑过。
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 typedef unsigned long long ull; 5 #define bpd cerr << "break point debug" << '\n' 6 #define deb(x) cerr << #x << ' ' << (x) << ' ' 7 #define Deb(x) cerr << #x << ' ' << (x) << '\n' 8 9 namespace Fast 10 { 11 const int MAXSIZE = 1 << 20; 12 char rbuf[MAXSIZE], pbuf[MAXSIZE], *p1, *p2, *pp = pbuf; 13 14 inline char gc() { if (p1 == p2) p2 = (p1 = rbuf) + fread(rbuf, 1, MAXSIZE, stdin); return p1 == p2 ? EOF : *p1++; } 15 inline void pc(const char &c) { if (pp - pbuf == MAXSIZE) fwrite(pbuf, 1, MAXSIZE, stdout), pp = pbuf; *pp++ = c; } 16 17 template<typename T> inline void read(T &x) 18 { 19 x = 0; bool f = false; char c = gc(); 20 while (c < '0' || c > '9') { if (c == '-') f = true; c = gc(); } 21 while (c >= '0' && c <= '9') x = x * 10 + (c ^ 48), c = gc(); 22 if (f) x = ~x + 1; 23 } 24 template<typename T> inline void print(T x) 25 { 26 static int sta[40], top = 0; 27 if (x < 0) x = -x, pc('-'); 28 do { sta[top++] = x % 10, x /= 10; } while (x); 29 while (top) pc(sta[--top] + '0'); 30 } 31 template<typename T> inline T Max(T x, T y) { return x > y ? x : y; } 32 template<typename T> inline T Min(T x, T y) { return x < y ? x : y; } 33 34 template<typename T, typename ...Args> inline void read(T &x, Args&... others) { read(x); read(others...); } 35 template<typename T, typename ...Args> inline T Max(T x, T y, Args... others) { return Max(Max(x, y), others...); } 36 template<typename T, typename ...Args> inline T Min(T x, T y, Args... others) { return Min(Min(x, y), others...); } 37 } 38 using namespace Fast; 39 40 const int N = 1e6 + 10; 41 int n, m, B, ans, a[N], cnt[N], pos[N], Ans[N]; 42 struct query 43 { 44 int l, r, id; 45 inline bool operator < (const query &x) const 46 { 47 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 48 } 49 } q[N]; 50 51 inline void add(int x) { if (!cnt[x]) ++ans; ++cnt[x]; } 52 inline void del(int x) { if (cnt[x] == 1) --ans; --cnt[x]; } 53 54 int main() 55 { 56 read(n); for (int i = 1; i <= n; ++i) read(a[i]); 57 read(m); B = ceil(sqrt(3) * n / sqrt(m)); 58 for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / B + 1; 59 for (int i = 1; i <= m; ++i) read(q[i].l, q[i].r), q[i].id = i; 60 sort(q + 1, q + m + 1); 61 for (int l = 1, r = 0, i = 1; i <= m; ++i) 62 { 63 while (l > q[i].l) add(a[--l]); 64 while (r < q[i].r) add(a[++r]); 65 while (l < q[i].l) del(a[l++]); 66 while (r > q[i].r) del(a[r--]); 67 Ans[q[i].id] = ans; 68 } 69 for (int i = 1; i <= m; ++i) print(Ans[i]), pc('\n'); 70 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 71 }
题意:给定长为 $n$ 的序列和 $m$ 次询问,每次询问区间随机抽取两个位置颜色相同的概率,$1\le n,m\le5\times10^4$,时限 0.2s。
模板题,展开公式后可以显然发现是求区间每种颜色个数平方和,注意 $n^2$ 会炸 $\texttt{int}$ 可以用 $\texttt{unsigned int}$。
1 const int N = 5e4 + 10; 2 int n, m, B, a[N], pos[N], cnt[N]; 3 unsigned int ans, Ans[N][2]; 4 struct query 5 { 6 int l, r, id; 7 inline bool operator < (const query &x) const 8 { 9 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 10 } 11 } q[N]; 12 13 inline void add(int x) { ans += 1 + (cnt[x] << 1), ++cnt[x]; } 14 inline void del(int x) { ans += 1 - (cnt[x] << 1), --cnt[x]; } 15 16 int main() 17 { 18 read(n, m), B = ceil(sqrt(3) * n / sqrt(m)); 19 for (int i = 1; i <= n; ++i) read(a[i]); 20 for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / B + 1; 21 for (int i = 1; i <= m; ++i) read(q[i].l, q[i].r), q[i].id = i; 22 sort(q + 1, q + m + 1); 23 for (int l = 1, r = 0, i = 1; i <= m; ++i) 24 { 25 while (l > q[i].l) add(a[--l]); 26 while (r < q[i].r) add(a[++r]); 27 while (l < q[i].l) del(a[l++]); 28 while (r > q[i].r) del(a[r--]); 29 if (l == r) Ans[q[i].id][0] = 0, Ans[q[i].id][1] = 1; 30 else 31 { 32 unsigned int len = r - l + 1, d; 33 Ans[q[i].id][0] = ans - len, Ans[q[i].id][1] = len * (len - 1); 34 d = __gcd(Ans[q[i].id][0], Ans[q[i].id][1]); 35 Ans[q[i].id][0] /= d, Ans[q[i].id][1] /= d; 36 } 37 } 38 for (int i = 1; i <= m; ++i) print(Ans[i][0]), pc('/'), print(Ans[i][1]), pc('\n'); 39 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 40 }
题意:给定长为 $n$ 的序列和 $m$ 次询问,每次询问区间众数的出现次数,$1\le n,m\le2\times10^5$,时限 1s。
题面有点乱,不难发现删除严格上升序列最优,严格上升序列的个数至少为区间众数的出现次数,所以就转化为上述题意。
设 $num[i]$ 表示出现次数为 $i$ 的数的个数,由于莫队每次只能插入或删除一个数,因此可以 $O(1)$ 更新答案。
1 const int N = 2e5 + 10; 2 int n, m, B, ans; 3 int a[N], b[N], pos[N], cnt[N], num[N], Ans[N]; 4 struct query 5 { 6 int l, r, id; 7 inline bool operator < (const query &x) const 8 { 9 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 10 } 11 } q[N]; 12 13 inline void add(int x) 14 { 15 if (ans == cnt[x] && !num[cnt[x] + 1]) ans = cnt[x] + 1; 16 --num[cnt[x]], ++num[++cnt[x]]; 17 } 18 inline void del(int x) 19 { 20 if (ans == cnt[x] && num[cnt[x]] == 1) ans = cnt[x] - 1; 21 --num[cnt[x]], ++num[--cnt[x]]; 22 } 23 24 int main() 25 { 26 read(n, m); B = ceil(sqrt(3) * n / sqrt(m)); 27 for (int i = 1; i <= n; ++i) read(a[i]), b[i] = a[i]; 28 sort(b + 1, b + n + 1); int k = unique(b + 1, b + n + 1) - b - 1; 29 for (int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + k + 1, a[i]) - b; 30 for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / B + 1; 31 for (int i = 1; i <= m; ++i) read(q[i].l, q[i].r), q[i].id = i; 32 sort(q + 1, q + m + 1); 33 for (int l = 1, r = 0, i = 1; i <= m; ++i) 34 { 35 while (l > q[i].l) add(a[--l]); 36 while (r < q[i].r) add(a[++r]); 37 while (l < q[i].l) del(a[l++]); 38 while (r > q[i].r) del(a[r--]); 39 Ans[q[i].id] = ans; 40 } 41 for (int i = 1; i <= m; ++i) pc('-'), print(Ans[i]), pc('\n'); 42 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 43 }
题意:给定长为 $n$ 的序列和 $m$ 次询问和一个定值 $k$,每次询问区间内异或和为 $k$ 的子区间个数,$1\le n,m,k\le10^5$,时限 1s。
前缀和后就转化为求有多少对位置 $(x,y)$ 满足 $a[x]\operatorname{xor}a[y]=k$,注意特判 $k=0$ 的情况。
1 const int N = 1 << 17; 2 int n, m, k, B, a[N], pos[N], cnt[N]; 3 ll ans, Ans[N]; 4 struct query 5 { 6 int l, r, id; 7 inline bool operator < (const query &x) const 8 { 9 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 10 } 11 } q[N]; 12 13 inline void add(int x) { ++cnt[x], ans += cnt[x ^ k] - !k; } 14 inline void del(int x) { ans -= cnt[x ^ k] - !k, --cnt[x]; } 15 16 int main() 17 { 18 read(n, m, k); B = ceil(sqrt(3) * n / sqrt(m)); 19 for (int i = 1; i <= n; ++i) read(a[i]), a[i] ^= a[i - 1]; 20 pos[0] = 1; for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / B + 1; 21 for (int i = 1; i <= m; ++i) read(q[i].l, q[i].r), --q[i].l, q[i].id = i; 22 sort(q + 1, q + m + 1); 23 for (int l = 0, r = -1, i = 1; i <= m; ++i) 24 { 25 while (l > q[i].l) add(a[--l]); 26 while (r < q[i].r) add(a[++r]); 27 while (l < q[i].l) del(a[l++]); 28 while (r > q[i].r) del(a[r--]); 29 Ans[q[i].id] = ans; 30 } 31 for (int i = 1; i <= m; ++i) print(Ans[i]), pc('\n'); 32 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 33 }
- 例题 5:P4396 [AHOI2013]作业(双倍经验:P4867 Gty的二逼妹子序列)
题意:给定长为 $n$ 的序列和 $m$ 次询问,每次询问区间 $[l,r]$ 值域 $[a,b]$ 的数的个数和数值的个数,$1\le n,m\le10^5$,时限 3s。
直接暴力上树状数组维护 $cnt[i]$ 可以做到 $O(n\sqrt{m}\log{n})$,卡一卡常数可以通过,缺点在于修改和查询都是 $O(\log{n})$ 的。
因此考虑用 $O(1)$ 修改 $O(\sqrt{n})$ 查询的值域分块维护 $cnt[i]$,时间复杂度被平衡为 $O(n\sqrt{m}+m\sqrt{n})$,可以轻松通过。
1 const int N = 1e5 + 10; 2 int n, m, B, a[N], pos[N], cnt[2][N], sum[2][500], Ans[N][2]; 3 struct query 4 { 5 int l, r, a, b, id; 6 inline bool operator < (const query &x) const 7 { 8 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 9 } 10 } q[N]; 11 12 inline void add(int x) 13 { 14 if (!cnt[0][x]) ++cnt[1][x], ++sum[1][pos[x]]; 15 ++cnt[0][x], ++sum[0][pos[x]]; 16 } 17 inline void del(int x) 18 { 19 --cnt[0][x], --sum[0][pos[x]]; 20 if (!cnt[0][x]) --cnt[1][x], --sum[1][pos[x]]; 21 } 22 inline void ask(int l, int r, int &x, int opt) 23 { 24 if (pos[l] == pos[r]) 25 for (int i = l; i <= r; ++i) x += cnt[opt][i]; 26 else 27 { 28 for (int i = l; i <= pos[l] * B; ++i) x += cnt[opt][i]; 29 for (int i = (pos[r] - 1) * B + 1; i <= r; ++i) x += cnt[opt][i]; 30 for (int i = pos[l] + 1; i < pos[r]; ++i) x += sum[opt][i]; 31 } 32 } 33 34 int main() 35 { 36 read(n, m); B = ceil(sqrt(3) * n / sqrt(m)); 37 for (int i = 1; i <= n; ++i) read(a[i]); 38 for (int i = 1; i <= 100000; ++i) pos[i] = (i - 1) / B + 1; 39 for (int i = 1; i <= m; ++i) read(q[i].l, q[i].r, q[i].a, q[i].b), q[i].id = i; 40 sort(q + 1, q + m + 1); 41 for (int l = 1, r = 0, i = 1; i <= m; ++i) 42 { 43 while (l > q[i].l) add(a[--l]); 44 while (r < q[i].r) add(a[++r]); 45 while (l < q[i].l) del(a[l++]); 46 while (r > q[i].r) del(a[r--]); 47 ask(q[i].a, q[i].b, Ans[q[i].id][0], 0); 48 ask(q[i].a, q[i].b, Ans[q[i].id][1], 1); 49 } 50 for (int i = 1; i <= m; ++i) print(Ans[i][0]), pc(' '), print(Ans[i][1]), pc('\n'); 51 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 52 }
- 例题 6:#6285. 数列分块入门 9
题意:给定长为 $n$ 的序列和 $n$ 次询问,每次询问区间最小众数,$1\le n\le10^5$,时限 1.5s。
例题 3 的难度加强版,直接暴力线段树在值域上维护 $\max{\{cnt[i]\}}$ 就能做到 $O(n\sqrt{n}\log{n})$,缺点是修改和查询都 $O(\log{n})$。
因此用 $O(1)$ 修改 $O(\sqrt{n})$ 查询的值域分块维护 $num[i]$ 和 $\max{\{cnt[i]\}}$,时间复杂度被平衡为 $O(n\sqrt{n})$,可以轻松通过。
1 const int N = 1e5 + 10; 2 const int S = 550; 3 int n, k, B, a[N], b[N], pos[N], cnt[N], num[S][S], maxcnt[S], Ans[N]; 4 struct query 5 { 6 int l, r, id; 7 inline bool operator < (const query &x) const 8 { 9 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 10 } 11 } q[N]; 12 13 inline void add(int x) 14 { 15 if (cnt[x] == maxcnt[pos[x]] && !num[pos[x]][cnt[x] + 1]) ++maxcnt[pos[x]]; 16 --num[pos[x]][cnt[x]], ++num[pos[x]][++cnt[x]]; 17 } 18 inline void del(int x) 19 { 20 if (cnt[x] == maxcnt[pos[x]] && num[pos[x]][cnt[x]] == 1) --maxcnt[pos[x]]; 21 --num[pos[x]][cnt[x]], ++num[pos[x]][--cnt[x]]; 22 } 23 inline int ask(int l, int r) 24 { 25 int p1 = 0, t1 = 0, p2 = 0, t2 = 0; 26 if (pos[l] == pos[r]) 27 { 28 for (int i = l; i <= r; ++i) 29 if (cnt[i] > t1) p1 = i, t1 = cnt[i]; 30 return p1; 31 } 32 for (int i = l; i <= pos[l] * B; ++i) 33 if (cnt[i] > t1) p1 = i, t1 = cnt[i]; 34 for (int i = pos[l] + 1; i < pos[r]; ++i) 35 if (maxcnt[i] > t2) p2 = i, t2 = maxcnt[i]; 36 if (t2 > t1) for (int i = (p2 - 1) * B + 1; i <= p2 * B; ++i) 37 if (cnt[i] == maxcnt[p2]) { p1 = i; t1 = cnt[i]; break; } 38 for (int i = (pos[r] - 1) * B + 1; i <= r; ++i) 39 if (cnt[i] > t1) p1 = i, t1 = cnt[i]; 40 return p1; 41 } 42 43 int main() 44 { 45 read(n); B = ceil(sqrt(3) * sqrt(n)); 46 for (int i = 1; i <= n; ++i) read(a[i]), b[i] = a[i]; 47 sort(b + 1, b + n + 1); k = unique(b + 1, b + n + 1) - b - 1; 48 for (int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + k + 1, a[i]) - b; 49 for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / B + 1; 50 for (int i = 1; i <= n; ++i) read(q[i].l, q[i].r), q[i].id = i; 51 sort(q + 1, q + n + 1); 52 for (int l = 1, r = 0, i = 1; i <= n; ++i) 53 { 54 while (l > q[i].l) add(a[--l]); 55 while (r < q[i].r) add(a[++r]); 56 while (l < q[i].l) del(a[l++]); 57 while (r > q[i].r) del(a[r--]); 58 Ans[q[i].id] = b[ask(1, k)]; 59 } 60 for (int i = 1; i <= n; ++i) print(Ans[i]), pc('\n'); 61 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 62 }
- 例题 7:AT1219 歴史の研究
题意:给定长为 $n$ 的序列和 $q$ 次询问,每次询问区间带权最大众数(数值与出现次数乘积最大),$1\le n,q\le10^5$,时限 4s。
例题 6 的难度加强版,也是这 7 道例题中最难的一道题(指普通莫队做法)。
由于带权导致答案与 $cnt[i]$ 的下标不相同,可能会从一个块跳到另一个块,普通的值域分块似乎无法处理。
但是考虑到区间 $\sum_i cnt[i]\le n$,也就是说答案不超过 $n$ 种情况,所以我们可以进行二次离散化,将给定的数值和可能出现的答案都分别离散化,然后再把离散化后的 $n$ 种答案当成值域去分块,那么问题就转化为:
给定一个 01 序列,初始全为 0,修改操作是将一个位置取反,询问的是 01 序列上最靠右的 1 的位置。
显然可以用 $O(1)$ 修改 $O(\sqrt{n})$ 查询的值域分块维护,时间复杂度被平衡为 $O(n\sqrt{q}+q\sqrt{n})$。
直觉上可能感觉常数非常巨大?但是实际上跑得飞快,吸氧后最慢的点大约是 330ms,比回滚莫队做法大概只慢了一倍。
当然这道题有更简单的做法——回滚莫队,详见 莫队学习笔记(2)回滚莫队,但是非常建议先用普通莫队 AC,会有很大收获。
1 const int N = 1e5 + 10; 2 const int S = 550; 3 int n, Q, k, B, a[N], b[N], pos[N], cnt[N], sum[S]; 4 bool f[N]; ll Ans[N]; 5 vector<int> vec[N]; 6 struct node 7 { 8 int a; ll val; 9 inline bool operator < (const node &x) const { return val < x.val; } 10 } c[N]; 11 struct query 12 { 13 int l, r, id; 14 inline bool operator < (const query &x) const 15 { 16 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 17 } 18 } q[N]; 19 20 inline void add(int x) 21 { 22 f[vec[x][cnt[x]]] = false, --sum[pos[vec[x][cnt[x]]]]; 23 f[vec[x][++cnt[x]]] = true, ++sum[pos[vec[x][cnt[x]]]]; 24 } 25 inline void del(int x) 26 { 27 f[vec[x][cnt[x]]] = false, --sum[pos[vec[x][cnt[x]]]]; 28 f[vec[x][--cnt[x]]] = true, ++sum[pos[vec[x][cnt[x]]]]; 29 } 30 inline int ask(int l, int r) 31 { 32 if (pos[l] == pos[r]) 33 for (int i = r; i >= l; --i) 34 if (f[i]) return i; 35 for (int i = r; i > (pos[r] - 1) * B; --i) 36 if (f[i]) return i; 37 for (int i = pos[r] - 1; i > pos[l]; --i) if (sum[i]) 38 for (int j = i * B; j > (i - 1) * B; --j) 39 if (f[j]) return j; 40 for (int i = pos[l] * B; i >= l; --i) 41 if (f[i]) return i; 42 } 43 44 int main() 45 { 46 read(n, Q); B = ceil(sqrt(3) * n / sqrt(Q)); 47 for (int i = 1; i <= n; ++i) read(a[i]), b[i] = a[i]; 48 sort(b + 1, b + n + 1); k = unique(b + 1, b + n + 1) - b - 1; 49 for (int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + k + 1, a[i]) - b; 50 for (int i = 1; i <= n; ++i) c[i] = (node){ a[i], 1ll * b[a[i]] * (++cnt[a[i]]) }; 51 memset(cnt, 0, sizeof(cnt)); sort(c + 1, c + n + 1); 52 for (int i = 1; i <= k; ++i) vec[i].emplace_back(0); 53 for (int i = 1; i <= n; ++i) vec[c[i].a].emplace_back(i); 54 for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / B + 1; 55 for (int i = 1; i <= Q; ++i) read(q[i].l, q[i].r), q[i].id = i; 56 sort(q + 1, q + Q + 1); 57 for (int l = 1, r = 0, i = 1; i <= Q; ++i) 58 { 59 while (l > q[i].l) add(a[--l]); 60 while (r < q[i].r) add(a[++r]); 61 while (l < q[i].l) del(a[l++]); 62 while (r > q[i].r) del(a[r--]); 63 Ans[q[i].id] = c[ask(1, n)].val; 64 } 65 for (int i = 1; i <= Q; ++i) print(Ans[i]), pc('\n'); 66 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 67 }
- 练习 1:P3901 数列找不同
提示:区间的数互不相同等价于区间颜色个数等于区间长度。
1 const int N = 1e5 + 10; 2 int n, Q, B, sum, a[N], pos[N], cnt[N], Ans[N]; 3 struct query 4 { 5 int l, r, id; 6 inline bool operator < (const query &x) const 7 { 8 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 9 } 10 } q[N]; 11 12 inline void add(int x) { if (!cnt[x]) ++sum; ++cnt[x]; } 13 inline void del(int x) { --cnt[x]; if (!cnt[x]) --sum; } 14 15 int main() 16 { 17 read(n, Q); B = ceil(sqrt(3) * n / sqrt(Q)); 18 for (int i = 1; i <= n; ++i) read(a[i]); 19 for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / B + 1; 20 for (int i = 1; i <= Q; ++i) read(q[i].l, q[i].r), q[i].id = i; 21 sort(q + 1, q + Q + 1); 22 for (int l = 1, r = 0, i = 1; i <= Q; ++i) 23 { 24 while (l > q[i].l) add(a[--l]); 25 while (r < q[i].r) add(a[++r]); 26 while (l < q[i].l) del(a[l++]); 27 while (r > q[i].r) del(a[r--]); 28 Ans[q[i].id] = sum == q[i].r - q[i].l + 1; 29 } 30 for (int i = 1; i <= Q; ++i) puts(Ans[i] ? "Yes" : "No"); 31 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 32 }
提示:注意是非降序列,因此连续出现次数就是在当前区间内的出现次数。
1 const int N = 2e5 + 10; 2 int n, Q, B, ans, a[N], pos[N], cnt[N], num[N], Ans[N]; 3 struct query 4 { 5 int l, r, id; 6 inline bool operator < (const query &x) const 7 { 8 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 9 } 10 } q[N]; 11 12 inline void add(int x) 13 { 14 if (ans == cnt[x]) ++ans; 15 --num[cnt[x]], ++num[++cnt[x]]; 16 } 17 inline void del(int x) 18 { 19 if (ans == cnt[x] && num[cnt[x]] == 1) --ans; 20 --num[cnt[x]], ++num[--cnt[x]]; 21 } 22 23 int main() 24 { 25 while (true) 26 { 27 read(n); if (!n) break; 28 read(Q); B = ceil(sqrt(3) * n / sqrt(Q)); 29 for (int i = 1; i <= n; ++i) read(a[i]), a[i] += 100000; 30 for (int i = 1; i <= n; ++i) cnt[a[i]] = 0, num[i] = 0; 31 for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / B + 1; 32 for (int i = 1; i <= Q; ++i) read(q[i].l, q[i].r), q[i].id = i; 33 sort(q + 1, q + Q + 1); ans = 0; 34 for (int l = 1, r = 0, i = 1; i <= Q; ++i) 35 { 36 while (l > q[i].l) add(a[--l]); 37 while (r < q[i].r) add(a[++r]); 38 while (l < q[i].l) del(a[l++]); 39 while (r > q[i].r) del(a[r--]); 40 Ans[q[i].id] = ans; 41 } 42 for (int i = 1; i <= Q; ++i) print(Ans[i]), pc('\n'); 43 } 44 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 45 }
- 练习 3:P2709 小B的询问
提示:$(a+1)^2-a^2=1+2a,\ (a-1)^2-a^2=1-2a$。
1 const int N = 5e4 + 10; 2 int n, m, k, B, a[N], pos[N], cnt[N]; 3 unsigned int ans, Ans[N]; 4 struct query 5 { 6 int l, r, id; 7 inline bool operator < (const query &x) const 8 { 9 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 10 } 11 } q[N]; 12 13 inline void add(int x) { ans += 1 + (cnt[x] << 1), ++cnt[x]; } 14 inline void del(int x) { ans += 1 - (cnt[x] << 1), --cnt[x]; } 15 16 int main() 17 { 18 read(n, m, k); B = ceil(sqrt(3) * n / sqrt(m)); 19 for (int i = 1; i <= n; ++i) read(a[i]); 20 for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / B + 1; 21 for (int i = 1; i <= m; ++i) read(q[i].l, q[i].r), q[i].id = i; 22 sort(q + 1, q + m + 1); 23 for (int l = 1, r = 0, i = 1; i <= m; ++i) 24 { 25 while (l > q[i].l) add(a[--l]); 26 while (r < q[i].r) add(a[++r]); 27 while (l < q[i].l) del(a[l++]); 28 while (r > q[i].r) del(a[r--]); 29 Ans[q[i].id] = ans; 30 } 31 for (int i = 1; i <= m; ++i) print(Ans[i]), pc('\n'); 32 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 33 }
- 练习 4:CF877F Ann and Books
提示:考虑差分思想将区间转化为前缀和问题,注意算上第 $0$ 个位置。
1 const int N = 3e5 + 10; 2 int n, m, Q, B, k, t[N], pos[N], cnt[N]; 3 ll ans, a[N], b[N], Ans[N]; 4 struct query 5 { 6 int l, r, id; 7 inline bool operator < (const query &x) const 8 { 9 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 10 } 11 } q[N]; 12 13 inline void addl(int i) { ans += cnt[a[n + i]], ++cnt[a[i]]; } 14 inline void addr(int i) { ans += cnt[a[(n << 1) + i]], ++cnt[a[i]]; } 15 inline void dell(int i) { --cnt[a[i]], ans -= cnt[a[n + i]]; } 16 inline void delr(int i) { --cnt[a[i]], ans -= cnt[a[(n << 1) + i]]; } 17 18 int main() 19 { 20 read(n, k), t[1] = 1, a[1] = 0, m = (++n) * 3; 21 for (int i = 2; i <= n; ++i) read(t[i]); 22 for (int i = 2; i <= n; ++i) read(a[i]); 23 for (int i = 1; i <= n; ++i) 24 { 25 if (t[i] == 2) a[i] = -a[i]; 26 b[i] = a[i] = a[i - 1] + a[i]; 27 b[n + i] = a[n + i] = a[i] + k; 28 b[(n << 1) + i] = a[(n << 1) + i] = a[i] - k; 29 } 30 sort(b + 1, b + m + 1); int len = unique(b + 1, b + m + 1) - b - 1; 31 for (int i = 1; i <= m; ++i) 32 a[i] = lower_bound(b + 1, b + len + 1, a[i]) - b; 33 read(Q); B = ceil(sqrt(3) * n / sqrt(Q)); 34 for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / B + 1; 35 for (int i = 1; i <= Q; ++i) 36 read(q[i].l, q[i].r), ++q[i].r, q[i].id = i; 37 sort(q + 1, q + Q + 1); 38 for (int l = 1, r = 0, i = 1; i <= Q; ++i) 39 { 40 while (l > q[i].l) addl(--l); 41 while (r < q[i].r) addr(++r); 42 while (l < q[i].l) dell(l++); 43 while (r > q[i].r) delr(r--); 44 Ans[q[i].id] = ans; 45 } 46 for (int i = 1; i <= Q; ++i) print(Ans[i]), pc('\n'); 47 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 48 }
提示:修改时用栈储存可能的答案,查询时暴力弹栈找合法答案即可,不难发现是 $O(1)$ 修改,均摊 $O(\dfrac{n}{\sqrt{m}})$ 查询。
1 const int N = 5e5 + 10; 2 int n, m, B, top, a[N], pos[N], cnt[N], sta[N], Ans[N]; 3 bool vis[N]; 4 struct query 5 { 6 int l, r, id; 7 inline bool operator < (const query &x) const 8 { 9 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 10 } 11 } q[N]; 12 13 inline void add(int x) { if (!vis[x] && !cnt[x]) vis[x] = true, sta[++top] = x; ++cnt[x]; } 14 inline void del(int x) { if (!vis[x] && cnt[x] == 2) vis[x] = true, sta[++top] = x; --cnt[x]; } 15 16 int main() 17 { 18 read(n); for (int i = 1; i <= n; ++i) read(a[i]); 19 read(m); B = ceil(sqrt(3) * n / sqrt(m)); 20 for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / B + 1; 21 for (int i = 1; i <= m; ++i) read(q[i].l, q[i].r), q[i].id = i; 22 sort(q + 1, q + m + 1); cnt[0] = 1, vis[0] = true; 23 for (int l = 1, r = 0, i = 1; i <= m; ++i) 24 { 25 while (l > q[i].l) add(a[--l]); 26 while (r < q[i].r) add(a[++r]); 27 while (l < q[i].l) del(a[l++]); 28 while (r > q[i].r) del(a[r--]); 29 while (cnt[sta[top]] != 1) vis[sta[top--]] = false; 30 Ans[q[i].id] = sta[top]; 31 } 32 for (int i = 1; i <= m; ++i) print(Ans[i]), pc('\n'); 33 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 34 }
提示:维护 $cnt[i]$ 来更新答案即可。
1 const int N = 1e5 + 10; 2 int n, m, k, B, ans, a[N], b[N], pos[N], cnt[N], Ans[N]; 3 struct query 4 { 5 int l, r, id; 6 inline bool operator < (const query &x) const 7 { 8 return pos[l] ^ pos[x.l] ? pos[l] < pos[x.l] : pos[l] & 1 ? r < x.r : r > x.r; 9 } 10 } q[N]; 11 12 inline void add(int x) 13 { 14 if (cnt[x] == b[x]) --ans; 15 ++cnt[x]; 16 if (cnt[x] == b[x]) ++ans; 17 } 18 inline void del(int x) 19 { 20 if (cnt[x] == b[x]) --ans; 21 --cnt[x]; 22 if (cnt[x] == b[x]) ++ans; 23 } 24 25 int main() 26 { 27 read(n, m); B = ceil(sqrt(3) * n / sqrt(m)); 28 for (int i = 1; i <= n; ++i) read(a[i]), b[i] = a[i]; 29 sort(b + 1, b + n + 1); k = unique(b + 1, b + n + 1) - b - 1; 30 for (int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + k + 1, a[i]) - b; 31 for (int i = 1; i <= n; ++i) pos[i] = (i - 1) / B + 1; 32 for (int i = 1; i <= m; ++i) read(q[i].l, q[i].r), q[i].id = i; 33 sort(q + 1, q + m + 1); 34 for (int l = 1, r = 0, i = 1; i <= m; ++i) 35 { 36 while (l > q[i].l) add(a[--l]); 37 while (r < q[i].r) add(a[++r]); 38 while (l < q[i].l) del(a[l++]); 39 while (r > q[i].r) del(a[r--]); 40 Ans[q[i].id] = ans; 41 } 42 for (int i = 1; i <= m; ++i) print(Ans[i]), pc('\n'); 43 fwrite(pbuf, 1, pp - pbuf, stdout); return 0; 44 }
To be continued...