莫队
我非常喜欢暴力算法
莫队最先由队长莫涛整理提出。是一种离线算法,处理区间询问。运用了分块的思想
适用性广。各种大佬扩展出了一系列的莫队算法。
复杂度分析玄学。在一些题正解难想、难写时,可考虑用莫队骗分,往往有意想不到的结果。
普通莫队
有两个指针 L,R
要求在 O(1) 转移到 [L,R−1],[L,R+1],[L+1,R],[L−1,R]
通过离线、对查询排序,使得暴力转移的复杂度可以接受。
采用分块 。以 l 所在块为第一关键字, r 为第二关键子排序。
复杂度:设块大小为 B ,则有 nB 个块
- 对于每一个块, r 是递增的,最坏移动 n 次,总共 n2B 次
- l 在同一块内单次移动是 O(B) 的,总共 O(mB)
- 跨块移动, r 最多跳 n 次 ,l 最多跳 2B 次,最多跨块 O(nB) 次,总共 O(n2B+2n)
三部分加起来,大致是 O(n2B+mB) 这个级别。由均值不等式,当 B=n√m 时取最优 O(n√m)
根据题目详情,设置块长,能得到更优的复杂度。
大致的实现:
- struct qry {
- int l, r, id;
- } q[N];
- inline bool cmp(qry A, qry B) {
- }
-
- // update current ans
- inline void add(int x) {
-
- }
- inline void del(int x) {
- }
-
- inline void solve() {
- sort(q + 1, q + T + 1, cmp);
- L = 1, R = 0;
- for (int i = 1, ql, qr, le; i <= T; i++) {
- ql = q[i].l, qr = q[i].r;
- while (L > ql) add(--L);
- while (L < ql) del(L++);
- while (R > qr) del(R--);
- while (R < qr) add(++R);
- ans[q[i].id] = now;
- }
- }
注意!
这四句话,由于 del 操作的存在,顺序需要注意
- while (L > ql) add(--L);
- while (L < ql) del(L++);
- while (R > qr) del(R--);
- while (R < qr) add(++R);
在一些题里,这种写法会有问题:移动中可能出现 L>R+1 的情况,相当于是多删除了一些数
而如果用 set 维护某些东西,会出现 “删除一个不存在的数” 的问题。
正确的顺序是:先进行 add ,再 del ,这样保证始终 L≤R+1 ,修改后代码如下:
- while (L > ql) add(--L);
- while (R < qr) add(++R);
- while (L < ql) del(L++);
- while (R > qr) del(R--);
根据题目,大部分情况,顺序任意。
奇偶排序
奇数块按 r 递增排序、偶数块按 r 递减排序。
优化了 R 指针跳的次数。
例
对于 n=m 的情况,块长取 √n 即可。
用一个桶 t 存,要求 ∑c12tc(tc−1)
- #include
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- const int N = 50005;
- int n, T, a[N], pos[N], bz[N];
- int size, L, R, tot[N];
- LL ans[N], now, all[N], G;
- struct qry {
- int l, r, id;
- } q[N];
- inline bool cmp(qry A, qry B) {
- if (pos[A.l] ^ pos[B.l]) return pos[A.l] < pos[B.l];
- if (pos[A.l] & 1) return A.r < B.r;
- return A.r > B.r;
- }
- inline void add(int x) {
- now -= tot[a[x]] * (tot[a[x]] - 1) / 2;
- ++tot[a[x]];
- now += tot[a[x]] * (tot[a[x]] - 1) / 2;
- }
- inline void del(int x) {
- now -= tot[a[x]] * (tot[a[x]] - 1) / 2;
- --tot[a[x]];
- now += tot[a[x]] * (tot[a[x]] - 1) / 2;
- }
- int main() {
- scanf("%d%d", &n, &T);
- size = sqrt(n);
- for (int i = 1; i <= n; i++) pos[i] = (i - 1) / size + 1;
- for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
- for (int i = 1; i <= T; i++) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
- sort(q + 1, q + T + 1, cmp);
- L = 1, R = 0;
- for (int i = 1, ql, qr, le; i <= T; i++) {
- ql = q[i].l, qr = q[i].r;
- le = qr - ql + 1;
- while (L > ql) add(--L);
- while (L < ql) del(L++);
- while (R > qr) del(R--);
- while (R < qr) add(++R);
- if (le == 1) bz[q[i].id] = 1;
- ans[q[i].id] = now;
- all[q[i].id] = 1ll * le * (le - 1) / 2;
- }
- for (int i = 1; i <= T; i++) {
- if (bz[i]) puts("0/1");
- else G = __gcd(ans[i], all[i]), printf("%lld/%lld\n", ans[i] / G, all[i] / G);
- }
- }
技巧
这部分
- inline void add(int x) {
- now -= tot[a[x]] * (tot[a[x]] - 1) / 2;
- ++tot[a[x]];
- now += tot[a[x]] * (tot[a[x]] - 1) / 2;
- }
- inline void del(int x) {
- now -= tot[a[x]] * (tot[a[x]] - 1) / 2;
- --tot[a[x]];
- now += tot[a[x]] * (tot[a[x]] - 1) / 2;
- }
会略显繁琐。实际可以化简
add 操作就是 now 要加上 12(tc+1)tc−12tc(tc−1) ,就是加上 tc
del 操作就是 now 要减上 12tc(tc−1)−12(tc−1)(tc−2) ,就是减去 tc−1
- inline void add(int x) {
- now += tot[a[x]], tot[a[x]]++;
- }
- inline void del(int x) {
- tot[a[x]]--, now -= tot[a[x]];
- }
实际上,这种化简是很常见的。也能优化常数,减小实现难度
带修莫队
带修改,查询颜色种数。
把修改当成第三维,记录当前是到第几次修改。
其实就是弄一个指针,再操作上移动,当前修改多了就改回来,修改少了就改过去。直到次数恰当。
(L,R,T) 到 (L,R,T+1) 和 (L,R,T−1) 是 O(1) 的。
具体就是修改后将修改值和原值 swap 一下,改回来也只需要 swap 一下,具体见代码
排序以 l 所在块、 r 所在块、 t 的值排序。
复杂度分析:设块长为 B ,修改 c 个,查询 q 个
-
l :同一块移动 B ,换块移动 2B ,所有询问加起来就是 qB
-
r :
- l 同块时,同块移动 B ,换块移动 2B ,所有询问就是 qB
- l 换块,最多 n ,换 nB 次,共 n2B
-
t :
- l,r 同块,移动 c ,有 (nB)2 次 l,r 同块,共 n2cB2
- l 或 r 换块,移动 c ,共 (nB)2 次换块,共 n2cB2
-
一般题目中不会明确 c,q 的个数,统一用 m 代替
总和: O(mB+n2B+n2mB2)
B 的最优取值是一个非常复杂的式子。无须纠结。
看作 n=m ,得到 O(nB+n2B+n3B2)
设 B=nx ,得到 O(n1+x+n2−x+n3−2x) ,要 max(1+x,2−x,3−2x) 最小
当 x=23 时取得,此时 B=n23 。复杂度为 O(n53)
- #include
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- const int N = 133335;
- int n, m, size, pos[N], a[N];
- int tq, clk, now, ans[N];
- int cnt[1000005];
- int L = 1, R = 0, T = 0;
- char op[5];
- struct qry {
- int l, r, t, id;
- } q[N];
- struct mdy {
- int p, c;
- } C[N];
- inline bool cmp(qry A, qry B) {
- if (pos[A.l] ^ pos[B.l]) return pos[A.l] < pos[B.l];
- if (pos[A.r] ^ pos[B.r]) return pos[A.r] < pos[B.r];
- return A.t < B.t;
- }
- inline void add(int x) { now += !cnt[a[x]]++; }
- inline void del(int x) { now -= !--cnt[a[x]]; }
- inline void chan(int t) {
- if (L <= C[t].p && C[t].p <= R) del(C[t].p);
- swap(a[C[t].p], C[t].c);
- if (L <= C[t].p && C[t].p <= R) add(C[t].p);
- }
- int main() {
- scanf("%d%d", &n, &m);
- size = pow(n, 2.0 / 3.0);
- for (int i = 1; i <= n; i++) pos[i] = (i - 1) / size + 1;
- for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
- for (int i = 1, a, b; i <= m; i++) {
- scanf("%s%d%d", op, &a, &b);
- if (op[0] == 'Q') {
- ++tq, q[tq] = (qry){ a, b, clk, tq };
- } else {
- ++clk, C[clk] = (mdy){ a, b };
- }
- }
- sort(q + 1, q + tq + 1, cmp);
- for (int i = 1, ql, qr, qt; i <= tq; i++) {
- ql = q[i].l, qr = q[i].r, qt = q[i].t;
- while (L < ql) del(L++);
- while (L > ql) add(--L);
- while (R < qr) add(++R);
- while (R > qr) del(R--);
- while (T < qt) chan(++T);
- while (T > qt) chan(T--);
- ans[q[i].id] = now;
- }
- for (int i = 1; i <= tq; i++) printf("%d\n", ans[i]);
- }
回滚莫队
在一些题目中,往往出现删除(或增加)非常容易。但是另一个操作却难以实现。
回滚莫队应运而生,解决这种情况
只增不减
-
数列分块,按 l 所在块、 r 的值升序排序。
-
l,r 再同一块,暴力处理。
-
对于同在块 T 的询问,将莫队左指针设为 RT+1 ,右指针设为 RT ,这是空区间
-
由于 r 升序排序,莫队右指针可以一直加,一个块最多 n
l 可能乱序,此时左指针从 RT+1 出发,加点到达询问位置。
回答完询问,撤销本次移动的影响,注意是撤销,使左端点回到 RT+1
每一个询问最多 √n
-
相同方式处理下一个块
复杂度依然是 O(n√n)
求 maxctc×c ,其中 tc 为出现次数。
用桶统计出现次数。加点时更新答案,撤销具体就是在桶中减去出现次数,而不修改答案。
- #include
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- const int N = 100005, SQ = 320;
- int n, m, a[N], tt[N], b[N], tl, Bsz, Btot, pr[SQ], po[N];
- int L = 1, R = 0, cnt[N], cur, tc[N];
- LL ans[N], tmp, now;
- struct qry { int l, r, id; } q[N];
- inline bool cmp(qry A, qry B) {
- if (po[A.l] ^ po[B.l]) return po[A.l] < po[B.l];
- return A.r < B.r;
- }
- inline void add(int x) {
- ++cnt[b[x]];
- now = max(now, 1ll * cnt[b[x]] * a[x]);
- }
- inline void cancel(int x) {
- --cnt[b[x]];
- }
- int main() {
- scanf("%d%d", &n, &m), Bsz = sqrt(n), Btot = n / Bsz;
- for (int i = 1; i <= n; i++) scanf("%d", &a[i]), tt[i] = a[i];
- sort(tt + 1, tt + n + 1), tl = unique(tt + 1, tt + n + 1) - tt - 1;
- for (int i = 1; i <= n; i++) b[i] = lower_bound(tt + 1, tt + tl + 1, a[i]) - tt;
- for (int i = 1; i <= Btot; i++) pr[i] = i * Bsz;
- if (pr[Btot] < n) pr[++Btot] = n;
- for (int i = 1; i <= n; i++) po[i] = (i - 1) / Bsz + 1;
- for (int i = 1; i <= m; i++) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
- sort(q + 1, q + m + 1, cmp);
-
- for (int i = 1, ql, qr; i <= m; i++) {
- ql = q[i].l, qr = q[i].r;
- if (po[ql] == po[qr]) {
- for (int j = ql; j <= qr; j++) ++tc[b[j]];
- tmp = 0;
- for (int j = ql; j <= qr; j++) tmp = max(tmp, 1ll * tc[b[j]] * a[j]);
- for (int j = ql; j <= qr; j++) --tc[b[j]];
- ans[q[i].id] = tmp;
- continue;
- }
- if (cur ^ po[ql]) {
- cur = po[ql];
- while (R > pr[cur]) cancel(R--);
- while (L < pr[cur] + 1) cancel(L++);
- now = 0;
- }
- while (R < qr) add(++R);
- tmp = now;
- while (L > ql) add(--L);
- while (L < pr[cur] + 1) cancel(L++);
- ans[q[i].id] = now, now = tmp;
- }
- for (int i = 1; i <= m; i++) printf("%lld\n", ans[i]);
- }
只减不增
同理。区别如下:
- 右端点降序排序
- 左端点初始为 LT ,右端点为 n
树上莫队
树上莫队一般是用于处理链的问题,子树的可以用 dfs 序解决。
链上数颜色。用欧拉序(括号序)转换。求
法是 dfs 得到一个点入栈、出栈顺序。分别记为 firstx,lastx
性质是:欧拉序上 [firstx,lastx] 之间的点都在子树 x 中,且都出现两次。
考虑将链转为欧拉序上区间问题。对于一条链 (u,v) ,假设 firstu≤firstv 即 u 比 v 先出现。
-
lca(u,v)=u ,说明 v 在子树 u 内,直接转为 [firstu,firstv]
同时区间内出现 2 次的点都不要。因为链不会经过一个点两次,经过了两次一定不在链上
-
lca(u,v)≠u ,用 [lastx,firstx] ,同时 lca(u,v) 没有算,需要单独考虑。
实现时:注意序列长度是 2n ,用一个数组记录是否出现即可实现取重。
- #include
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- const int N = 100005;
- int n, T, tt[N], a[N], lt, lst[N], Ecnt, pos[N], size, vis[N];
- int fr[N], la[N], clk, fa[N][20], dep[N], ord[N], ans[N], cnt[N];
- int L = 1, R = 0, now;
- struct qry {
- int l, r, x, id;
- } q[N];
- inline bool cmp(qry A, qry B) {
- if (pos[A.l] ^ pos[B.l]) return pos[A.l] < pos[B.l];
- if (pos[A.l] & 1) return A.r < B.r;
- return A.r > B.r;
- }
- struct Ed { int to, nxt; } e[N];
- inline void Ae(int fr, int go) {
- e[++Ecnt] = (Ed){ go, lst[fr] }, lst[fr] = Ecnt;
- }
- void dfs(int u, int ff) {
- fa[u][0] = ff, dep[u] = dep[ff] + 1;
- for (int i = 1; i <= 16; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1];
- ord[++clk] = u, fr[u] = clk;
- for (int i = lst[u], v; i; i = e[i].nxt)
- if ((v = e[i].to) ^ ff) dfs(v, u);
- ord[++clk] = u, la[u] = clk;
- }
- inline int LCA(int x, int y) {
- if (dep[x] < dep[y]) x ^= y ^= x ^= y;
- for (int i = 16; ~i; i--)
- if (dep[fa[x][i]] >= dep[y]) x = fa[x][i];
- if (x == y) return x;
- for (int i = 16; ~i; i--)
- if (fa[x][i] ^ fa[y][i]) x = fa[x][i], y = fa[y][i];
- return fa[x][0];
- }
- inline void work(int x) {
- if (vis[x]) now -= !--cnt[a[x]];
- else now += !cnt[a[x]]++;
- vis[x] ^= 1;
- }
- int main() {
- scanf("%d%d", &n, &T);
- for (int i = 1; i <= n; i++) scanf("%d", &tt[i]), a[i] = tt[i];
- sort(tt + 1, tt + n + 1);
- lt = unique(tt + 1, tt + n + 1) - tt - 1;
- for (int i = 1; i <= n; i++)
- a[i] = lower_bound(tt + 1, tt + lt + 1, a[i]) - tt;
- for (int i = 1, u, v; i < n; i++) {
- scanf("%d%d", &u, &v);
- Ae(u, v), Ae(v, u);
- }
- dfs(1, 0);
- size = sqrt(clk);
- for (int i = 1; i <= clk; i++) pos[i] = (i - 1) / size + 1;
- for (int i = 1, u, v, ll; i <= T; i++) {
- scanf("%d%d", &u, &v);
- ll = LCA(u, v);
- if (fr[u] > fr[v]) u ^= v ^= u ^= v;
- if (u == ll) q[i] = (qry){ fr[u], fr[v], 0, i };
- else q[i] = (qry){ la[u], fr[v], ll, i };
- }
- sort(q + 1, q + T + 1, cmp);
- for (int i = 1, ql, qr, qx; i <= T; i++) {
- ql = q[i].l, qr = q[i].r, qx = q[i].x;
- while (L < ql) work(ord[L++]);
- while (L > ql) work(ord[--L]);
- while (R < qr) work(ord[++R]);
- while (R > qr) work(ord[R--]);
- if (qx) work(qx);
- ans[q[i].id] = now;
- if (qx) work(qx);
- }
- for (int i = 1; i <= T; i++) printf("%d\n", ans[i]);
- }
二维莫队
YALI 往年集训题处的做法。
D2T2 蔬菜
求矩阵中颜色出现次数的平方和。
查询放到了矩阵中。按照四个端点所在块排序。
不同于普通莫队,这里一次修改为 O(n) 的。
块长随缘了,分析累了。
但是码量、速度碾标算的四维偏序
#include using namespace std;typedef unsigned long long uLL;typedef long double LD;typedef long long LL;typedef double db;const int N = 205;int n, m, Ti, a[N][N], b[N * N], le, t[N * N], now, Bsz;struct qry { int xx, yy, x2, y2, id;} q[100005];int ans[100005];inline bool cmp(qry A, qry B) { if (A.xx / Bsz != B.xx / Bsz) return A.xx / Bsz < B.xx / Bsz; if (A.yy / Bsz != B.yy / Bsz) return A.yy / Bsz < B.yy / Bsz; if (A.x2 / Bsz != B.x2 / Bsz) return A.x2 / Bsz < B.x2 / Bsz; return A.y2 < B.y2;}inline void add(int x) { now += 1 + 2 * t[x], ++t[x]; }inline void del(int x) { now += 1 - 2 * t[x], --t[x]; }inline void pls(int i, int l, int r, int op) { for (int j = l; j <= r; j++) add(op ? a[i][j] : a[j][i]);}inline void rmv(int i, int l, int r, int op) { for (int j = l; j <= r; j++) del(op ? a[i][j] : a[j][i]);}int main() { // freopen("vegetable.in", "r", stdin); // freopen("vegetable.out", "w", stdout); scanf("%d%d%d", &n, &m, &Ti); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) scanf("%d", &a[i][j]), b[++le] = a[i][j]; Bsz = sqrt(max(n, m)); sort(b + 1, b + le + 1), le = unique(b + 1, b + le + 1) - b - 1; for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) a[i][j] = lower_bound(b + 1, b + le + 1, a[i][j]) - b; for (int i = 1; i <= Ti; i++) scanf("%d%d%d%d", &q[i].xx, &q[i].yy, &q[i].x2, &q[i].y2), q[i].id = i; sort(q + 1, q + Ti + 1, cmp); int li = 1, ri = 0, lj = 1, rj = 0; for (int i = 1, ql, qr; i <= Ti; i++) { while (li < q[i].xx) rmv(li++, lj, rj, 1); while (li > q[i].xx) pls(--li, lj, rj, 1); while (ri < q[i].x2) pls(++ri, lj, rj, 1); while (ri > q[i].x2) rmv(ri--, lj, rj, 1); while (lj < q[i].yy) rmv(lj++, li, ri, 0); while (lj > q[i].yy) pls(--lj, li, ri, 0); while (rj < q[i].y2) pls(++rj, li, ri, 0); while (rj > q[i].y2) rmv(rj--, li, ri, 0); ans[q[i].id] = now; } for (int i = 1; i <= Ti; i++) printf("%d\n", ans[i]);}
莫队二次离线
先 % 提出者 lxl ,用于解决莫队转移复杂度高的情况
求区间内满足条件数对个数,普通莫队是 O(n√n(14k)) ,难以接受,但是注意到可以差分
设 f(x,l,r) 表示 x 与 [l,r] 中的点形成的合法数对个数,莫队的指针有四种移动方法,讨论一波。
设莫队指针为 L,R ,询问区间为 ql,qr
- L<ql ,从 [L,R] 到 [L+1,R] ,减去 f(L,L+1,R)=f(L,1,R)−f(L,1,L)
- L>ql ,从 [L,R] 到 [L−1,R] ,加上 f(L−1,L,R)=f(L−1,1,R)−f(L−1,1,L−1)
- R<qr ,从 [L,R] 到 [L,R+1] ,加上 f(R+1,L,R)=f(R+1,1,R)−f(R+1,1,L−1)
- R>qr ,从 [L,R] 到 [L,R−1] ,减去 f(R,L,R−1)=f(R,1,R−1)−f(R,1,L−1)
发现如果与处理出 f(x,1,x) 和 f(x,1,x−1) ,就解决了一半。
移动指针时加(或减)即可。前缀和优化减小常数
剩下的一部分的贡献和:形如 ∑rx=lf(x,1,pos) ,二次离线,在 pos 处挂一个 (l,r,id,op)
其中 id 是询问编号, op 是 1 或 -1 的系数。枚举每一个位置,暴力求解即可。
由于考虑的是变化量,所以答案应该求前缀和。
复杂度是 O(n√n+n(14k))
- #include
- #define pb push_back
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- const int N = 100005;
- int n, m, K, a[N], b[N], Tb, cnt[N], pos[N], Bsz;
- LL pre1[N], pre2[N], ans[N], out[N];
- struct qry { int l, r, id; } q[N];
- struct add { int l, r, id, op; } nw;
- vector
cf[N]; - inline bool cmp(qry A, qry B) {
- if (pos[A.l] ^ pos[B.l]) return pos[A.l] < pos[B.l];
- return A.r < B.r;
- }
- int main() {
- scanf("%d%d%d", &n, &m, &K);
- for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
- if (K > 14) {
- for (int i = 1; i <= m; i++) puts("0");
- return 0;
- }
- for (int i = 0, j, t; i < 16384; i++) {
- j = i, t = 0; while (j) j -= j & -j, ++t;
- if (t == K) b[++Tb] = i;
- }
- // pre1 : f(i, 1, i - 1)
- // pre2 : f(i, 1, i)
- for (int i = 1; i <= n; i++) {
- pre1[i] = pre1[i - 1] + cnt[a[i]];
- for (int j = 1; j <= Tb; j++) ++cnt[a[i] ^ b[j]];
- pre2[i] = pre2[i - 1] + cnt[a[i]];
- }
- Bsz = sqrt(n);
- for (int i = 1; i <= n; i++) pos[i] = (i - 1) / Bsz + 1;
- for (int i = 1; i <= m; i++) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
- sort(q + 1, q + m + 1, cmp);
- for (int i = 1, L = 1, R = 0, ql, qr; i <= m; i++) {
- ql = q[i].l, qr = q[i].r;
- ans[i] = pre2[ql - 1] - pre2[L - 1] + pre1[qr] - pre1[R];
- if (L < ql) cf[R].pb((add){ L, ql - 1, i,-1 });
- if (L > ql) cf[R].pb((add){ ql, L - 1, i, 1 });
- L = ql;
- // attention : pointer L moves to ql first
- if (R < qr) cf[L - 1].pb((add){ R + 1, qr, i,-1 });
- if (R > qr) cf[L - 1].pb((add){ qr + 1, R, i, 1 });
- R = qr;
- // -= f(L, L + 1, R) = -f(L, 1, R) + f(L, 1, L);
- // += f(L - 1, L, R) = +f(L - 1, 1, R) - f(L - 1, 1, L - 1)
- // += f(R + 1, L, R) = +f(R + 1, 1, R) - f(R + 1, 1, L - 1);
- // -= f(R, L, R - 1) = -f(R, 1, R - 1) + f(R, 1, L - 1);
- }
- memset(cnt, 0, sizeof(cnt));
- for (int i = 1; i <= n; i++) {
- for (int j = 1; j <= Tb; j++) cnt[a[i] ^ b[j]]++;
- for (int j = 0, Lj = cf[i].size(); j < Lj; j++) {
- nw = cf[i][j];
- register LL tmp = 0;
- for (int k = nw.l; k <= nw.r; k++) tmp += cnt[a[k]];
- ans[nw.id] += nw.op * tmp;
- }
- }
- for (int i = 1; i <= m; i++) ans[i] += ans[i - 1], out[q[i].id] = ans[i];
- for (int i = 1; i <= m; i++) printf("%lld\n", out[i]);
- }
莫队加 bitset
bitset 有 n64 的复杂度
往往解决区间内和/差/积/max等值域相关问题
[Ynoi2017] 由乃的玉米田 (比上一题多了除法)
查询是否存在 a,b 满足相加/减/乘/除等于 x
和/差用 bitset 解决,乘积直接暴力。
-
减法:相当于存在 (a−x,a) 用一个
bitsets1 ,第 i 位记录是否存在 i 。如果
s1 & (s1 << x)有任意一位是 1 ,说明可行。 -
加法:相当于存在 (x−a,a) ,需要一个反的
bitsets2 ,记录是否存在 max−i那么
s2 >> (max - k)得到是否存在 (max−i)−(max−k)=k−i 了。s1 & (s2 >> (max - k))存在 1 说明可行 -
乘法:分解质因数。根号级别。
根号分治
解决除法操作:
-
x≤√max ,将所有满足情况的 x 存下来单独处理。
对于同一个 x ,枚举所有数,维护 lstv 表示 v 最后出现位置,
还有 mli 表示以 i 为右端点最大的左端点,使得 [mli,i] 内存在满足条件的数对。
-
否则,暴力枚举 x 的倍数
code
时间复杂度:O(n√n+T×max64+T√max) ,十分的鬼畜。。
给出 由乃的玉米田 的代码
- #include
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- const int N = 100005;
- int n, T, a[N], pos[N], unit, ans[N], cnt[N], L = 1, R = 0, mx, lim, lst[N], ml[N], Qtot;
- bitset
s1, s2; - struct qry { int l, r, x, op, id; } q[N];
- vector
q2[320]; - inline bool cmp(qry A, qry B) {
- if (pos[A.l] ^ pos[B.l]) return pos[A.l] < pos[B.l];
- return A.r < B.r;
- }
- inline void add(int x) {
- ++cnt[x = a[x]];
- if (cnt[x] == 1) s1[x] = s2[100000 - x] = 1;
- }
- inline void del(int x) {
- --cnt[x = a[x]];
- if (!cnt[x]) s1[x] = s2[100000 - x] = 0;
- }
- int main() {
- scanf("%d%d", &n, &T);
- unit = sqrt(n);
- for (int i = 1; i <= n; i++) pos[i] = (i - 1) / unit + 1;
- for (int i = 1; i <= n; i++) scanf("%d", &a[i]), mx = max(mx, a[i]);
- lim = sqrt(mx);
- for (int i = 1, op, l, r, x; i <= T; i++) {
- scanf("%d%d%d%d", &op, &l, &r, &x);
- if (op == 4 && x <= lim) q2[x].push_back((qry){ l, r, x, op, i });
- else q[++Qtot] = (qry){ l, r, x, op, i };
- }
- for (int w = 1; w <= lim; w++) {
- memset(lst, 0, sizeof(lst));
- memset(ml, 0, sizeof(ml));
- if (q2[w].empty()) continue;
- for (int i = 1, p = 0; i <= n; i++) {
- lst[a[i]] = i;
- if (w * a[i] <= mx) p = max(p, lst[w * a[i]]);
- if (a[i] % w == 0) p = max(p, lst[a[i] / w]);
- ml[i] = p;
- }
- for (qry qq : q2[w]) ans[qq.id] = (qq.l <= ml[qq.r]);
- }
- sort(q + 1, q + Qtot + 1, cmp);
- for (int i = 1, ql, qr, qx; i <= Qtot; i++) {
- ql = q[i].l, qr = q[i].r, qx = q[i].x;
- while (L < ql) del(L++);
- while (L > ql) add(--L);
- while (R < qr) add(++R);
- while (R > qr) del(R--);
- if (q[i].op == 1) ans[q[i].id] = (s1 & (s1 >> qx)).any();
- else if (q[i].op == 2) ans[q[i].id] = (s1 & (s2 >> (n - qx))).any();
- else if (q[i].op == 3) {
- for (int j = 1; j * j <= qx; j++)
- if (qx % j == 0)
- if (s1[j] && s1[qx / j]) {
- ans[q[i].id] = 1; break;
- }
- } else {
- for (int j = 1; j * qx <= mx; j++)
- if (s1[j] && s1[j * qx]) { ans[q[i].id] = 1; break; }
- }
- }
- for (int i = 1; i <= T; i++) puts(ans[i] ? "yuno" : "yumi");
- }
应用
[WC2013] 糖果公园
树上询问路径,求 ∑cvalc∑cntci=1wi ,带修改。
树上带修莫队,弄出欧拉序后写带修莫队,板子题,锻炼代码实现。。
- #include
- #define ri register int
- using namespace std;
- char buf[100000], *Sf = buf, *Tf = buf;
- inline char G() {
- return Sf == Tf && (Tf = (Sf = buf) + fread(buf, 1, 100000, stdin), Sf == Tf) ? EOF : *Sf++;
- }
- inline int Rd() {
- ri x = 0;
- static char C = G();
- for (; C < '0' || C > '9'; C = G()) ;
- for (; C > '/' && C < ':'; C = G()) x = (x << 1) + (x << 3) + (C ^ 48);
- return x;
- }
- typedef long long LL;
- const int N = 200005;
- int n, m, Ti, v[N], w[N], a[N], lst[N], Ecnt;
- int fr[N], la[N], ord[N], clk, Tc, Tq, sz[N], top[N];
- int fa[N], dep[N], Bs, pos[N], vis[N], sn[N];
- int L, R, T, cnt[N];
- LL ans[N], now;
- struct qry { int l, r, t, x, id; } q[N];
- struct chg { int p, x; } C[N];
- struct Ed { int to, nxt; } e[N << 1];
- inline void Ae(ri fr, ri go) {
- e[++Ecnt] = (Ed){ go, lst[fr] }, lst[fr] = Ecnt;
- }
- void dfs(ri u, ri ff) {
- fr[u] = ++clk, ord[clk] = u;
- dep[u] = dep[fa[u] = ff] + 1, sz[u] = 1;
- for (ri i = lst[u], v; i; i = e[i].nxt)
- if ((v = e[i].to) ^ ff) {
- dfs(v, u), sz[u] += sz[v];
- if (sz[v] > sz[sn[u]]) sn[u] = v;
- }
- la[u] = ++clk, ord[clk] = u;
- }
- void dfs2(ri u, ri nw) {
- top[u] = nw; if (!sn[u]) return; dfs2(sn[u], nw);
- for (ri i = lst[u], v; i; i = e[i].nxt)
- if ((v = e[i].to) ^ fa[u] && v ^ sn[u]) dfs2(v, v);
- }
- inline int LCA(ri x, ri y) {
- for (;top[x] ^ top[y]; x = fa[top[x]])
- if (dep[top[x]] < dep[top[y]]) x ^= y ^= x ^= y;
- return dep[x] < dep[y] ? x : y;
- }
- inline bool cmp(qry A, qry B) {
- if (pos[A.l] ^ pos[B.l]) return pos[A.l] < pos[B.l];
- if (pos[A.r] ^ pos[B.r]) return pos[A.r] < pos[B.r];
- return A.t < B.t;
- }
- inline void add(ri x) { now += 1ll * w[++cnt[x]] * v[x]; }
- inline void del(ri x) { now -= 1ll * w[cnt[x]--] * v[x]; }
- inline void work(ri x) { (vis[x] ^= 1) ? add(a[x]) : del(a[x]); }
- inline void chan(ri t) {
- if (vis[C[t].p]) del(a[C[t].p]), add(C[t].x);
- swap(a[C[t].p], C[t].x);
- }
- int main() {
- n = Rd(), m = Rd(), Ti = Rd();
- for (ri i = 1; i <= m; i++) v[i] = Rd();
- for (ri i = 1; i <= n; i++) w[i] = Rd();
- for (ri i = 1, u, v; i < n; i++) {
- u = Rd(), v = Rd(), Ae(u, v), Ae(v, u);
- }
- for (ri i = 1; i <= n; i++) a[i] = Rd();
- dfs(1, 0), dfs2(1, 1);
- for (ri op, x, y, ff; Ti--; ) {
- op = Rd(), x = Rd(), y = Rd();
- if (!op) ++Tc, C[Tc] = (chg){ x, y };
- else {
- ff = LCA(x, y), ++Tq;
- if (fr[x] > fr[y]) x ^= y ^= x ^= y;
- if (x == ff) q[Tq] = (qry){ fr[x], fr[y], Tc, 0, Tq };
- else q[Tq] = (qry){ la[x], fr[y], Tc, ff, Tq };
- }
- }
- Bs = pow(clk, 2.0 / 3.0);
- for (ri i = 1; i <= clk; i++) pos[i] = (i - 1) / Bs + 1;
- sort(q + 1, q + Tq + 1, cmp);
- L = 1, R = 0, T = 0;
- for (ri i = 1, ql, qr, qt; i <= Tq; i++) {
- ql = q[i].l, qr = q[i].r, qt = q[i].t;
- while (L < ql) work(ord[L++]);
- while (L > ql) work(ord[--L]);
- while (R < qr) work(ord[++R]);
- while (R > qr) work(ord[R--]);
- while (T < qt) chan(++T);
- while (T > qt) chan(T--);
- if (q[i].x) work(q[i].x);
- ans[q[i].id] = now;
- if (q[i].x) work(q[i].x);
- }
- for (ri i = 1; i <= Tq; i++) printf("%lld\n", ans[i]);
- }
[SNOI2017]一个简单的询问
求 ∑xget(l1,r1,x)×get(l2,r2,x) ,其中 get(l,r,x) 表示 [l,r] 中 x 的个数
差分,得 ∑x(get(1,r1,x)−get(1,l1−1,x))(get(1,r2,x)−get(1,l2−1,x))
令 g(p,x)=get(1,p,x) ,
得 ∑xg(r1,x)g(r2,x)−g(r1,x)g(l2−1,x)−g(l1−1,x)g(r2,x)+g(l1−1,x)g(l2−1,x)
拆成四个询问。用两个数组维护。
这道题其实是运用了莫队的思想,并不是经典的莫队。
- #include
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- const int N = 50005;
- int n, a[N], tq, T, sz, pos[N], cnt[N][2];
- LL ans[N], now;
- int L = 0, R = 0;
- struct qry {
- int a, b, op, id;
- } q[N << 2];
- inline bool cmp(qry A, qry B) {
- if (pos[A.a] != pos[B.a]) return pos[A.a] < pos[B.a];
- return A.b < B.b;
- }
- inline void add(int x, int w) {
- now += 1ll * cnt[a[x]][w];
- ++cnt[a[x]][w ^ 1];
- }
- inline void del(int x, int w) {
- now -= 1ll * cnt[a[x]][w];
- --cnt[a[x]][w ^ 1];
- }
- int main() {
- scanf("%d", &n);
- sz = sqrt(n);
- for (int i = 1; i <= n; i++) pos[i] = (i - 1) / sz + 1;
- for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
- scanf("%d", &T);
- for (int i = 1, a, b, c, d; i <= T; i++) {
- scanf("%d%d%d%d", &a, &b, &c, &d);
- q[++tq] = (qry){ a - 1, c - 1, 1, i };
- q[++tq] = (qry){ a - 1, d, -1, i };
- q[++tq] = (qry){ b, d, 1, i };
- q[++tq] = (qry){ b, c - 1, -1, i };
- }
- for (int i = 1; i <= tq; i++)
- if (q[i].a > q[i].b) swap(q[i].a, q[i].b);
- sort(q + 1, q + tq + 1, cmp);
- for (int i = 1, ql, qr; i <= tq; i++) {
- ql = q[i].a, qr = q[i].b;
- while (L < ql) add(++L, 0);
- while (L > ql) del(L--, 0);
- while (R < qr) add(++R, 1);
- while (R > qr) del(R--, 1);
- ans[q[i].id] += now * q[i].op;
- }
- for (int i = 1; i <= T; i++) printf("%lld\n", ans[i]);
- }
[Ynoi2016] 这是我自己的发明
前置知识:上一题。将询问放到了子树上,用 dfs 序转换。
换根对于一个 x 只有两种情况:根在不在子树 x 中(当根为 1 时)
对于根在子树 x 的情况其实就是整一棵树挖掉 x 的某一个儿子所在子树。
这个儿子所在子树是包含根的,开 map 按 dfn 挂上所有儿子,用新的根的 dfn 一次 lower_bound 即可。
询问要么是一个区间,要么是 [1,n] 挖掉一个区间
设 f([a,b],[c,d]) 表示区间 [a,b] 和 [c,d] 的答案,prei=f([1,i],[1,n]) 有两种情况较特殊
和
发现其实只用求 f([l,r],[L,R]) ,只拆成四个询问即可
- #include
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- const int N = 100005, M = 500005;
- int n, T, a[N], b[N], lst[N], ecnt, clk, st[N], ed[N], Rt;
- int tq, pos[N], sz, cnt[N][2], L = 0, R = 0, tc[N];
- LL now, ans[M], pre[N];
- struct qry {
- int a, b, op, id;
- } q[M << 2];
- struct Ed { int to, nxt; } e[N << 1];
- inline void Ae(int fr, int go) {
- e[++ecnt] = (Ed){ go, lst[fr] }, lst[fr] = ecnt;
- }
- map<int, int> ch[N];
- void dfs(int u, int ff) {
- st[u] = ++clk;
- for (int i = lst[u], v; i; i = e[i].nxt)
- if ((v = e[i].to) ^ ff) dfs(v, u), ch[u][ed[v]] = v;
- ed[u] = clk;
- }
- inline bool cmp(qry A, qry B) {
- if (pos[A.a] ^ pos[B.a]) return pos[A.a] < pos[B.a];
- return A.b < B.b;
- }
- inline void add(int x, int w) {
- ++cnt[a[x]][w];
- now += cnt[a[x]][w ^ 1];
- }
- inline void del(int x, int w) {
- --cnt[a[x]][w];
- now -= cnt[a[x]][w ^ 1];
- }
- int main() {
- scanf("%d%d", &n, &T);
- sz = n / sqrt(T);
- for (int i = 1; i <= n; i++) pos[i] = (i - 1) / sz + 1;
- for (int i = 1; i <= n; i++) scanf("%d", &a[i]), b[i] = a[i];
- sort(a + 1, a + n + 1);
- tq = unique(a + 1, a + n + 1) - a - 1;
- for (int i = 1; i <= n; i++)
- b[i] = lower_bound(a + 1, a + tq + 1, b[i]) - a;
- for (int i = 1, u, v; i < n; i++) {
- scanf("%d%d", &u, &v);
- Ae(u, v), Ae(v, u);
- }
- Rt = 1;
- dfs(1, 0);
- tq = 0;
- for (int i = 1; i <= n; i++) ++tc[a[st[i]] = b[i]];
- for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + tc[a[i]];
- for (int i = 1, op, x, y, tx, ty, a, b, c, d; i <= T; i++) {
- scanf("%d%d", &op, &x);
- if (op == 1) Rt = x, --i, --T;
- else {
- scanf("%d", &y);
- tx = (st[x] <= st[Rt] && ed[Rt] <= ed[x]); if (x == Rt) x = 1, tx = 0;
- ty = (st[y] <= st[Rt] && ed[Rt] <= ed[y]); if (y == Rt) y = 1, ty = 0;
- if (tx) x = ch[x].lower_bound(st[Rt])->second;
- if (ty) y = ch[y].lower_bound(st[Rt])->second;
- a = st[x] - 1, b = ed[x], c = st[y] - 1, d = ed[y];
- if (tx && ty) ans[i] += pre[n];
- op = (tx == ty ? -1 : 1);
- if (tx) ans[i] += (pre[d] - pre[c]) * op;
- if (ty) ans[i] += (pre[b] - pre[a]) * op;
- q[++tq] = (qry){ a, c, -op, i };
- q[++tq] = (qry){ a, d, op, i };
- q[++tq] = (qry){ b, d, -op, i };
- q[++tq] = (qry){ b, c, op, i };
- }
- }
- sort(q + 1, q + tq + 1, cmp);
- for (int i = 1, ql, qr; i <= tq; i++) {
- ql = q[i].a, qr = q[i].b;
- while (L < ql) add(++L, 0);
- while (L > ql) del(L--, 0);
- while (R < qr) add(++R, 1);
- while (R > qr) del(R--, 1);
- ans[q[i].id] += now * q[i].op;
- }
- for (int i = 1; i <= T; i++) printf("%lld\n", ans[i]);
- }
Gty的二逼妹子序列
求区间内值在 [a,b] 范围内的数的个数。
我会树状数组!
O(n√nlogn) ,会超时。
根号平衡
运用分块的优越性质:分块可以实现 O(1) 插入, O(√n) 查询。或者 O(√n) 插入 O(1) 查询。
莫队需要大量的插入操作,这使得复杂度均匀为 O(logn) 的 bit 难以承受。
这到题,对值域分块,实现 O(1) 插入 O(√n) 查询,总共 O((n+m)√n)
- #include
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- const int N = 100005, M = 1000005, SQ = 1005;
- int n, m, a[N], Bsz, Btot, pos[N], pl[SQ], pr[SQ];
- int L, R, ans[M], cnt[N], sm[SQ];
- struct qry { int l, r, a, b, id; } q[M];
- inline bool cmp(qry A, qry B) {
- if (pos[A.l] ^ pos[B.l]) return pos[A.l] < pos[B.l];
- return A.r < B.r;
- }
- inline void add(int x) {
- if (!cnt[x]++) ++sm[pos[x]];
- }
- inline void del(int x) {
- if (!--cnt[x]) --sm[pos[x]];
- }
- inline int ask(int l, int r) {
- register int p = pos[l], q = pos[r], re = 0;
- if (p == q) {
- for (int i = l; i <= r; i++) re += (cnt[i] > 0);
- return re;
- }
- for (int i = l; i <= pr[p]; i++) re += (cnt[i] > 0);
- for (int i = pl[q]; i <= r; i++) re += (cnt[i] > 0);
- for (int i = p + 1; i < q; i++) re += sm[i];
- return re;
- }
- int main() {
- scanf("%d%d", &n, &m);
- for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
- for (int i = 1; i <= m; i++) scanf("%d%d%d%d", &q[i].l, &q[i].r, &q[i].a, &q[i].b), q[i].id = i;
- Bsz = sqrt(n), Btot = (n - 1) / Bsz + 1;
- for (int i = 1; i <= n; i++) pos[i] = (i - 1) / Bsz + 1;
- for (int i = 1; i <= Btot; i++) pl[i] = pr[i - 1] + 1, pr[i] = min(n, i * Bsz);
- sort(q + 1, q + m + 1, cmp);
- L = 1, R = 0;
- for (int i = 1, ql, qr; i <= m; i++) {
- ql = q[i].l, qr = q[i].r;
- while (L < ql) del(a[L++]);
- while (L > ql) add(a[--L]);
- while (R < qr) add(a[++R]);
- while (R > qr) del(a[R--]);
- ans[q[i].id] = ask(q[i].a, q[i].b);
- }
- for (int i = 1; i <= m; i++) printf("%d\n", ans[i]);
- }
[Ynoi2019] Yuno loves sqrt technology II
[Ynoi2019 模拟赛] Yuno loves sqrt technology II
先 % 出题人 lxl
求区间逆序对个数。
暴力:树状数组转移,O(n√nlogn) ,在神仙数据下只有。。20 pts
转移复杂度神仙,考虑二次离线。
剩下的就比较模板了,但是如果还用 bit 的话,大量的查询导致超时。
需要记录一个向前形成的逆序对和一个向后形成的逆序对
还记得根号平衡?用 O(√n) 插入 O(1) 查询的分块就可以了。
- #include
- #define pb push_back
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- const int N = 100005, SQ = 325;
- int n, m, a[N], b[N], Tb, pl[SQ], pr[SQ], po[N], Bsz, Btot;
- int s1[SQ], s2[SQ], t1[N], t2[N];
- LL pre1[N], pre2[N], ans[N], out[N];
- // 1 : front
- // 2 : back
- struct qry { int l, r, id; } q[N];
- struct mdy { int l, r, id, op, vl; } nw;
- vector
d[N]; - inline bool cmp(qry A, qry B) {
- if (po[A.l] ^ po[B.l]) return po[A.l] < po[B.l];
- if (po[A.l] & 1) return A.r < B.r;
- return A.r > B.r;
- }
- inline void clear() {
- memset(s1, 0, sizeof(s1)), memset(t1, 0, sizeof(t1));
- memset(s2, 0, sizeof(s2)), memset(t2, 0, sizeof(t2));
- }
- inline void add(int x) {
- register int p = po[x];
- for (int i = 1; i < p; i++) ++s1[i];
- for (int i = p + 1; i <= Btot; i++) ++s2[i];
- for (int i = pl[p]; i < x; i++) ++t1[i];
- for (int i = x + 1; i <= pr[p]; i++) ++t2[i];
- }
- inline int ask1(int x) { return s1[po[x]] + t1[x]; }
- inline int ask2(int x) { return s2[po[x]] + t2[x]; }
- int main() {
- scanf("%d%d", &n, &m);
- for (int i = 1; i <= n; i++) scanf("%d", &a[i]), b[i] = a[i];
- sort(b + 1, b + n + 1), Tb = unique(b + 1, b + n + 1) - b - 1;
- for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + Tb + 1, a[i]) - b;
- Bsz = sqrt(n);
- for (int i = 1; i <= n; i++) po[i] = (i - 1) / Bsz + 1;
- Btot = po[n];
- for (int i = 1; i <= Btot; i++) pl[i] = pr[i - 1] + 1, pr[i] = i * Bsz;
- pr[Btot] = n;
- for (int i = 1; i <= m; i++) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
- sort(q + 1, q + m + 1, cmp);
- for (int i = 1; i <= n; i++) {
- pre1[i] = pre1[i - 1] + ask1(a[i]);
- pre2[i] = pre2[i - 1] + ask2(a[i]);
- add(a[i]);
- }
- for (int i = 1, ql, qr, L = 1, R = 0; i <= m; i++) {
- ql = q[i].l, qr = q[i].r;
- ans[i] = pre2[ql - 1] - pre2[L - 1] + pre1[qr] - pre1[R];
- if (L < ql) d[qr].pb((mdy){ L, ql - 1, i, 1,-1 });
- if (L > ql) d[qr].pb((mdy){ ql, L - 1, i, 1, 1 });
- if (R < qr) d[L - 1].pb((mdy){ R + 1, qr, i, 0,-1 });
- if (R > qr) d[L - 1].pb((mdy){ qr + 1, R, i, 0, 1 });
- L = ql, R = qr;
- }
- clear();
- for (int i = 1; i <= n; i++) {
- add(a[i]);
- for (int j = 0, Lj = d[i].size(); j < Lj; j++) {
- nw = d[i][j];
- register LL tmp = 0;
- for (int k = nw.l; k <= nw.r; k++) {
- if (nw.op) tmp += ask2(a[k]);
- else tmp += ask1(a[k]);
- }
- ans[nw.id] += tmp * nw.vl;
- }
- }
- for (int i = 1; i <= m; i++) ans[i] += ans[i - 1], out[q[i].id] = ans[i];
- for (int i = 1; i <= m; i++) printf("%lld\n", out[i]);
- }
[Ynoi2015] 此时此刻的光辉
求区间内所有数乘积的约数个数 (mod19260817)
n≤105,ai≤109
用约数个数定理计算,Pollard-Rho 分解质因数后莫队可以直接算贡献。
却发现: 109 级别的质数个数,难以保证复杂度。
想到一个定理:一个数 v 只会有最多一个 ≥√v 的质因子。
预处理出 ≤√v 的质数,然后求前缀和?
又发现:√109 约等于 31622 ,大约有 3401 个质数,查询和预处理都是 O(3401n) ,难以接受。
扩展一下那个定理:一个数 v 最多只会有两个 ≥3√v 的质因子。
而 3√109=1000 ,大约只有 168 个质数, O(168n) 的预处理是可行的。
剩下的两个因子,用调用一次 PR 就可以了,不要写 dfs ,否则被卡常。
将大于 1000 的因子存下来,离散化是必然的
由于一个数剩下最多两个因子,保证莫队更新的复杂度为 O(1) , ≤1000 的因子用前缀和查询即可
卡常难度在 PR ,主要是我的 MR 是 O(log2) 的。。。
注意预处理逆元需要预处理到 2n ,每个数会有两个 ≥1000 的质因子
- #include
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- char buf[100000], *S = buf, *T = buf;
- inline char Gc() {
- return S == T && (T = (S = buf) + fread(buf, 1, 100000, stdin), S == T) ? EOF : *S++;
- }
- inline int Rd() {
- register int x = 0, C = Gc();
- for (; C < '0' || C > '9'; C = Gc()) ;
- for (; C > '/' && C < ':'; C = Gc()) x = (x << 1) + (x << 3) + (C ^ 48);
- return x;
- }
- inline int Pow(int x, int y, int p) {
- register int res = 1;
- for (; y; y >>= 1, x = 1ll * x * x % p)
- if (y & 1) res = 1ll * res * x % p;
- return res;
- }
- inline bool Mr(LL n, LL p) {
- if (Pow(p, n - 1, n) != 1) return 0;
- register LL q = n - 1, o;
- while (!(q & 1)) {
- q >>= 1, o = Pow(p, q, n);
- if (o != 1 && o != n - 1) return 0;
- if (o == n - 1) return 1;
- }
- return 1;
- }
- inline bool Prime(int n) {
- if (n < 2) return false;
- if (n == 2 || n == 3 || n == 5 || n == 7 || n == 43) return true;
- return Mr(n, 2) && Mr(n, 3) && Mr(n, 5) && Mr(n, 7) && Mr(n, 43);
- }
- int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
- inline int Rho(int n) {
- register int p, q, x, y, z, c, g;
- for (;;) {
- x = y = rand() % n, c = rand() % n;
- p = 0, q = z = 1;
- while (++p) {
- x = (1ll * x * x % n + c) % n;
- z = 1ll * z * abs(y - x) % n;
- if (x == y || !z) break;
- if (!(p % 127) || p == q) {
- g = gcd(z, n);
- if (g > 1) return g;
- if (p == q) q <<= 1, y = x;
- }
- }
- }
- }
- const int P = 19260817, N = 2e5 + 5;
- int n, m, id[1005], Pcnt, pr[1005], s[N][1005], a[N], b[N], Bsz, ans[N], now, inv[N], tt[N], pp[N], Pl;
- struct qry { int l, r, id; } q[N];
- inline bool cmp(qry A, qry B) {
- if (A.l / Bsz != B.l / Bsz) return A.l < B.l;
- return (A.l / Bsz) & 1 ? A.r > B.r : A.r < B.r;
- }
- inline void ad(int v) { now = 1ll * now * inv[tt[v]] % P * (tt[v] + 1) % P, tt[v]++; }
- inline void dl(int v) { now = 1ll * now * inv[tt[v]] % P * (tt[v] - 1) % P, tt[v]--; }
- inline void add(int p) { if (a[p]) ad(a[p]); if (b[p]) ad(b[p]); }
- inline void del(int p) { if (a[p]) dl(a[p]); if (b[p]) dl(b[p]); }
- int main() {
- for (int i = 1; i <= 1000; i++) id[i] = 1;
- for (int i = 2; i <= 1000; i++) {
- if (!id[i]) continue; pr[id[i] = ++Pcnt] = i;
- for (int j = i + i; j <= 1000; j += i) id[j] = 0;
- }
- inv[1] = 1;
- for (int i = 2; i <= 200000; i++)
- inv[i] = 1ll * (P - P / i) * inv[P % i] % P;
- n = Rd(), m = Rd();
- for (int i = 1, x, y; i <= n; i++) {
- x = Rd();
- memcpy(s[i], s[i - 1], sizeof(s[i]));
- for (int j = 1; j <= Pcnt && pr[j] * pr[j] <= x; j++)
- while (x % pr[j] == 0) x /= pr[j], s[i][j]++;
- if (x > 1) {
- if (x <= pr[Pcnt]) ++s[i][id[x]];
- else {
- if (!Prime(x))
- b[i] = pp[++Pl] = y = Rho(x), x /= y;
- if (x > 1) a[i] = x, pp[++Pl] = x;
- }
- }
- }
- sort(pp + 1, pp + Pl + 1);
- Pl = unique(pp + 1, pp + Pl + 1) - pp - 1;
- for (int i = 1; i <= n; i++) {
- if (a[i]) a[i] = lower_bound(pp + 1, pp + Pl + 1, a[i]) - pp;
- if (b[i]) b[i] = lower_bound(pp + 1, pp + Pl + 1, b[i]) - pp;
- }
- Bsz = sqrt(n);
- for (int i = 1; i <= m; i++) q[i].l = Rd(), q[i].r = Rd(), q[i].id = i;
- sort(q + 1, q + m + 1, cmp);
- for (int i = 1; i <= n * 2; i++) tt[i] = 1;
- int L = 1, R = 0;
- now = 1;
- for (int i = 1, ql, qr; i <= m; i++) {
- ql = q[i].l, qr = q[i].r;
- while (L > ql) add(--L);
- while (R < qr) add(++R);
- while (L < ql) del(L++);
- while (R > qr) del(R--);
- ans[q[i].id] = now;
- for (int j = 1; j <= Pcnt; j++)
- ans[q[i].id] = 1ll * ans[q[i].id] * (s[qr][j] - s[ql - 1][j] + 1) % P;
- }
- for (int i = 1; i <= m; i++) printf("%d\n", ans[i]);
- }
[Ynoi2015] 盼君勿忘
莫队只是工具,以思维难度为主。
求区间内子序列分别取重后的和 (modp)
假设数 x 在区间 [l,r] 内出现 k 次,那么包含 x 的子序列有 2r−l+1−2r−l+1−k 个
所以 x 的贡献就是 x×(2r−l+1−2r−l+1−k)
设 si 为出现次数为 i 的和, cnti 为数 i 出现次数。
双向链表维护出现次数为 i 的数的和,不同的 i 值最多 √n 个。遍历一次链表可以算出答案。
复杂度为 O((n+m)√n)
光速幂
每一个询问模数不同,快速幂?平白多处一个 log ?预处理?直接 O(n) ?
引出黑科技:光速幂。
对于每一个询问,预处理 20,21,⋯,2√n−1 和 2√n,22√n,⋯,2n
求一个 2p ,用 p=k√n+l(0≤l<√n) 计算即可。
预处理 O(√n) ,查询 O(1)
- #include
- #define pb push_back
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- char buf[100000], *S = buf, *T = buf;
- inline char Gc() {
- return S == T && (T = (S = buf) + fread(buf, 1, 100000, stdin), S == T) ? EOF : *S++;
- }
- inline int rd() {
- register int x = 0;
- static char C = Gc();
- for (; C < '0' || x > '9'; C = Gc()) ;
- for (; C > '/' && C < ':'; C = Gc()) x = (x << 1) + (x << 3) + (C ^ 48);
- return x;
- }
- const int N = 100005;
- int n, m, a[N], Bsz, Btot, po[N], L = 1, R = 0;
- int pre[N], nxt[N], Ed;
- LL sm[N], ans[N], cnt[N], p1[N], p2[N], v1, v2;
- struct qry { int l, r, p, id; } q[N];
- inline bool cmp(qry A, qry B) {
- if (po[A.l] ^ po[B.l]) return po[A.l] < po[B.l];
- if (po[A.l] & 1) return A.r < B.r;
- return A.r > B.r;
- }
- inline void ins(int x) {
- nxt[Ed] = x, pre[x] = Ed, Ed = x;
- }
- inline void dlt(int x) {
- if (x != Ed) nxt[pre[x]] = nxt[x], pre[nxt[x]] = pre[x];
- else nxt[pre[x]] = 0, Ed = pre[x];
- pre[x] = nxt[x] = 0;
- }
- inline int pow2(int x, int P) {
- return p2[x / Bsz] * p1[x % Bsz] % P;
- }
- inline void add(int x) {
- sm[cnt[x]] -= x;
- if (!sm[cnt[x]]) dlt(cnt[x]);
- ++cnt[x];
- if (!sm[cnt[x]]) ins(cnt[x]);
- sm[cnt[x]] += x;
- }
- inline void del(int x) {
- sm[cnt[x]] -= x;
- if (!sm[cnt[x]]) dlt(cnt[x]);
- --cnt[x];
- if (!sm[cnt[x]]) ins(cnt[x]);
- sm[cnt[x]] += x;
- }
- inline void init(int P) {
- p1[0] = p2[0] = 1;
- for (int i = 1; i <= Bsz; i++)
- p1[i] = 2 * p1[i - 1] % P;
- for (int i = 1; i <= Btot; i++)
- p2[i] = p2[i - 1] * p1[Bsz] % P;
- }
- int main() {
- n = rd(), m = rd(), Bsz = sqrt(n);
- for (int i = 1; i <= n; i++) a[i] = rd(), po[i] = (i - 1) / Bsz + 1;
- Btot = po[n];
- for (int i = 1; i <= m; i++) q[i] = (qry){ rd(), rd(), rd(), i };
- sort(q + 1, q + m + 1, cmp);
- for (int i = 1, ql, qr, P; i <= m; i++) {
- ql = q[i].l, qr = q[i].r, P = q[i].p;
- init(P);
- while (L < ql) del(a[L++]);
- while (L > ql) add(a[--L]);
- while (R < qr) add(a[++R]);
- while (R > qr) del(a[R--]);
- for (int j = nxt[0]; j; j = nxt[j]) {
- v1 = pow2(qr - ql + 1, P);
- v2 = pow2(qr - ql + 1 - j, P);
- (ans[q[i].id] += sm[j] * (v1 - v2 + P) % P) %= P;
- }
- }
- for (int i = 1; i <= m; i++) printf("%lld\n", ans[i]);
- }
『MdOI R1』Path
trie 显然。
结论:对于两条不相交的路径,存在一点 u 使得一条路径在子树 u 内,一条在子树外。
转为 dfs 序,将数组复制一份,对于每一个 u ,相当于两个询问。
由于是求 max ,用只增不减的回滚莫队即可。
O(n√nlogw)
- #include
- using namespace std;
- typedef unsigned long long uLL;
- typedef long double LD;
- typedef long long LL;
- typedef double db;
- const int N = 100005;
- int n, lst[N], Ecnt, xo[N], dfn[N], clk, sz[N], unit, pos[N], Btot, pr[N], tq, dis[N];
- int ch[N * 30][2], tg[N * 30], Ttot, L, R, now, tmp, ans[N], out, cur, ed[N], rk[N];
- struct Ed { int to, nxt, qz; } e[N << 1];
- inline void Ae(int fr, int go, int vl) {
- e[++Ecnt] = (Ed){ go, lst[fr], vl }, lst[fr] = Ecnt;
- }
- struct qry {
- int l, r, id;
- } q[N];
- inline bool cmp(qry A, qry B) {
- if (pos[A.l] ^ pos[B.l]) return pos[A.l] < pos[B.l];
- return A.r < B.r;
- }
- void dfs(int u, int ff) {
- dfn[u] = ++clk, rk[clk] = u, sz[u] = 1;
- for (int i = lst[u], v; i; i = e[i].nxt)
- if ((v = e[i].to) ^ ff)
- dis[v] = dis[u] ^ e[i].qz, dfs(v, u), sz[u] += sz[v];
- ed[u] = clk;
- }
- inline void ins(int x) {
- register int u = 0;
- for (int i = 30, s; ~i; i--) {
- s = (x >> i) & 1;
- if (!ch[u][s]) ch[u][s] = ++Ttot;
- u = ch[u][s], ++tg[u];
- }
- }
- inline int ask(int x) {
- register int u = 0, re = 0;
- for (int i = 30, s; ~i; i--) {
- s = (x >> i) & 1;
- if (!tg[ch[u][s ^ 1]]) u = ch[u][s];
- else u = ch[u][s ^ 1], re |= 1 << i;
- }
- return re;
- }
- inline void cancel(int x) {
- register int u = 0;
- for (int i = 30, s; ~i; i--) {
- s = (x >> i) & 1;
- u = ch[u][s], --tg[u];
- }
- }
- int main() {
- scanf("%d", &n);
- for (int i = 1, u, v, w; i < n; i++) {
- scanf("%d%d%d", &u, &v, &w);
- Ae(u, v, w), Ae(v, u, w);
- }
- dfs(1, 0);
- for (int i = 2; i <= n; i++) {
- q[++tq] = (qry){ dfn[i], ed[i], i };
- q[++tq] = (qry){ ed[i] + 1, dfn[i] + n - 1, i };
- }
- for (int i = 1; i <= n; i++) xo[i] = xo[i + n] = dis[rk[i]];
- n <<= 1, unit = sqrt(n), Btot = n / unit;
- for (int i = 1; i <= n; i++) pos[i] = (i - 1) / unit + 1;
- for (int i = 1; i <= Btot; i++) pr[i] = i * unit;
- if (pr[Btot] < n) pr[++Btot] = n;
- sort(q + 1, q + tq + 1, cmp);
- // init
- L = 1, R = 0;
- for (int i = 1, ql, qr; i <= tq; i++) {
- ql = q[i].l, qr = q[i].r;
- if (cur ^ pos[ql]) {
- cur = pos[ql], now = 0;
- while (L <= R) cancel(xo[L++]);
- L = pr[cur] + 1, R = pr[cur];
- }
- if (pos[ql] == pos[qr]) {
- tmp = 0;
- for (int j = ql; j <= qr; j++) ins(xo[j]);
- for (int j = ql; j <= qr; j++) tmp = max(tmp, ask(xo[j]));
- for (int j = ql; j <= qr; j++) cancel(xo[j]);
- ans[q[i].id] += tmp;
- continue;
- }
- while (R < qr) ins(xo[++R]), now = max(now, ask(xo[R]));
- tmp = now;
- while (L > ql) ins(xo[--L]), now = max(now, ask(xo[L]));
- while (L < pr[cur] + 1) cancel(xo[L++]);
- ans[q[i].id] += now, now = tmp;
- }
- for (int i = 2; i <= n; i++) out = max(out, ans[i]);
- printf("%d\n", out);
- }
