莫队

莫队

我非常喜欢暴力算法

莫队最先由队长莫涛整理提出。是一种离线算法,处理区间询问。运用了分块的思想

适用性广。各种大佬扩展出了一系列的莫队算法。

复杂度分析玄学。在一些题正解难想、难写时,可考虑用莫队骗分,往往有意想不到的结果。

普通莫队

有两个指针 L,R 表示当前维护 [L,R] 内的答案

要求在 O(1) 转移到 [L,R1],[L,R+1],[L+1,R],[L1,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=nm 时取最优 O(nm)

根据题目详情,设置块长,能得到更优的复杂度。

大致的实现:

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 ,这样保证始终 LR+1 ,修改后代码如下:

        while (L > ql) add(--L);
        while (R < qr) add(++R);
        while (L < ql) del(L++);
        while (R > qr) del(R--);

根据题目,大部分情况,顺序任意。

奇偶排序

奇数块按 r 递增排序、偶数块按 r 递减排序。

优化了 R 指针跳的次数。

[国家集训队] 小 Z 的袜子

对于 n=m 的情况,块长取 n 即可。

用一个桶 t 存,要求 c12tc(tc1)

#include <bits/stdc++.h>
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)tc12tc(tc1) ,就是加上 tc

del 操作就是 now 要减上 12tc(tc1)12(tc1)(tc2) ,就是减去 tc1

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,T1)O(1) 的。

具体就是修改后将修改值和原值 swap 一下,改回来也只需要 swap 一下,具体见代码

排序以 l 所在块、 r 所在块、 t 的值排序。

复杂度分析:设块长为 B ,修改 c 个,查询 q

  • l :同一块移动 B ,换块移动 2B ,所有询问加起来就是 qB

  • r

    1. l 同块时,同块移动 B ,换块移动 2B ,所有询问就是 qB
    2. l 换块,最多 n ,换 nB 次,共 n2B
  • t

    1. l,r 同块,移动 c ,有 (nB)2l,r 同块,共 n2cB2
    2. lr 换块,移动 c ,共 (nB)2 次换块,共 n2cB2
  • 一般题目中不会明确 c,q 的个数,统一用 m 代替

总和: O(mB+n2B+n2mB2)

B 的最优取值是一个非常复杂的式子。无须纠结。

看作 n=m ,得到 O(nB+n2B+n3B2)

B=nx ,得到 O(n1+x+n2x+n32x) ,要 max(1+x,2x,32x) 最小

x=23 时取得,此时 B=n23 。复杂度为 O(n53)

#include <bits/stdc++.h>
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]);
}

回滚莫队

在一些题目中,往往出现删除(或增加)非常容易。但是另一个操作却难以实现。

回滚莫队应运而生,解决这种情况

只增不减

  1. 数列分块,按 l 所在块、 r 的值升序排序。

  2. l,r 再同一块,暴力处理。

  3. 对于同在块 T 的询问,将莫队左指针设为 RT+1 ,右指针设为 RT ,这是空区间

  4. 由于 r 升序排序,莫队右指针可以一直加,一个块最多 n

    l 可能乱序,此时左指针从 RT+1 出发,加点到达询问位置。

    回答完询问,撤销本次移动的影响,注意是撤销,使左端点回到 RT+1

    每一个询问最多 n

  5. 相同方式处理下一个块

复杂度依然是 O(nn)

歴史の研究

maxctc×c ,其中 tc 为出现次数。

用桶统计出现次数。加点时更新答案,撤销具体就是在桶中减去出现次数,而不修改答案。

#include <bits/stdc++.h>
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]);
}

只减不增

同理。区别如下:

  1. 右端点降序排序
  2. 左端点初始为 LT ,右端点为 n

树上莫队

树上莫队一般是用于处理链的问题,子树的可以用 dfs 序解决。

COT2 - Count on a tree II

链上数颜色。用欧拉序(括号序)转换。求

法是 dfs 得到一个点入栈、出栈顺序。分别记为 firstx,lastx

性质是:欧拉序上 [firstx,lastx] 之间的点都在子树 x 中,且都出现两次。

考虑将链转为欧拉序上区间问题。对于一条链 (u,v) ,假设 firstufirstvuv 先出现。

  1. lca(u,v)=u ,说明 v 在子树 u 内,直接转为 [firstu,firstv]

    同时区间内出现 2 次的点都不要。因为链不会经过一个点两次,经过了两次一定不在链上

  2. lca(u,v)u ,用 [lastx,firstx] ,同时 lca(u,v) 没有算,需要单独考虑。

实现时:注意序列长度是 2n ,用一个数组记录是否出现即可实现取重。

#include <bits/stdc++.h>
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 <bits/stdc++.h>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(nn(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][L1,R] ,加上 f(L1,L,R)=f(L1,1,R)f(L1,1,L1)
  • R<qr ,从 [L,R][L,R+1] ,加上 f(R+1,L,R)=f(R+1,1,R)f(R+1,1,L1)
  • R>qr ,从 [L,R][L,R1] ,减去 f(R,L,R1)=f(R,1,R1)f(R,1,L1)

发现如果与处理出 f(x,1,x)f(x,1,x1) ,就解决了一半。

移动指针时加(或减)即可。前缀和优化减小常数

剩下的一部分的贡献和:形如 x=lrf(x,1,pos) ,二次离线,在 pos 处挂一个 (l,r,id,op)

其中 id 是询问编号, op 是 1 或 -1 的系数。枚举每一个位置,暴力求解即可。

由于考虑的是变化量,所以答案应该求前缀和。

复杂度是 O(nn+n(14k))

#include <bits/stdc++.h>
#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<add> 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 解决,乘积直接暴力。

  • 减法:相当于存在 (ax,a) 用一个 bitset s1 ,第 i 位记录是否存在 i

    如果 s1 & (s1 << x) 有任意一位是 1 ,说明可行。

  • 加法:相当于存在 (xa,a) ,需要一个反的 bitset s2 ,记录是否存在 maxi

    那么 s2 >> (max - k) 得到是否存在 (maxi)(maxk)=ki 了。

    s1 & (s2 >> (max - k)) 存在 1 说明可行

  • 乘法:分解质因数。根号级别。

根号分治

解决除法操作:

  1. xmax ,将所有满足情况的 x 存下来单独处理。

    对于同一个 x ,枚举所有数,维护 lstv 表示 v 最后出现位置,

    还有 mli 表示以 i 为右端点最大的左端点,使得 [mli,i] 内存在满足条件的数对。

  2. 否则,暴力枚举 x 的倍数

code

时间复杂度:O(nn+T×max64+Tmax) ,十分的鬼畜。。

给出 由乃的玉米田 的代码

#include <bits/stdc++.h>
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<N> s1, s2;
struct qry { int l, r, x, op, id; } q[N];
vector<qry> 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] 糖果公园

[WC2013] 糖果公园

树上询问路径,求 cvalci=1cntcwi ,带修改。

树上带修莫队,弄出欧拉序后写带修莫队,板子题,锻炼代码实现。。

#include <bits/stdc++.h>
#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]一个简单的询问

[SNOI2017]一个简单的询问

xget(l1,r1,x)×get(l2,r2,x) ,其中 get(l,r,x) 表示 [l,r]x 的个数

差分,得 x(get(1,r1,x)get(1,l11,x))(get(1,r2,x)get(1,l21,x))

g(p,x)=get(1,p,x)

xg(r1,x)g(r2,x)g(r1,x)g(l21,x)g(l11,x)g(r2,x)+g(l11,x)g(l21,x)

拆成四个询问。用两个数组维护。

这道题其实是运用了莫队的思想,并不是经典的莫队。

#include <bits/stdc++.h>
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] 这是我自己的发明

[Ynoi2016] 这是我自己的发明

前置知识:上一题。将询问放到了子树上,用 dfs 序转换。

换根对于一个 x 只有两种情况:根在不在子树 x 中(当根为 1 时)

对于根在子树 x 的情况其实就是整一棵树挖掉 x 的某一个儿子所在子树。

这个儿子所在子树是包含根的,开 mapdfn 挂上所有儿子,用新的根的 dfn 一次 lower_bound 即可。

询问要么是一个区间,要么是 [1,n] 挖掉一个区间

f([a,b],[c,d]) 表示区间 [a,b][c,d] 的答案,prei=f([1,i],[1,n]) 有两种情况较特殊

f([l,r],[1,L)(R,n])=f([l,r],[1,n])f([l,r],[L,R])=prerprelf([l,r],[L,R])

f([1,l)(r,n],[1,L)(R,n])=f([1,n],[1,n])f([l,r],[1,n])f([1,n],[L,R])+f([l,r],[L,R])=

发现其实只用求 f([l,r],[L,R]) ,只拆成四个询问即可

#include <bits/stdc++.h>
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的二逼妹子序列

Gty的二逼妹子序列

求区间内值在 [a,b] 范围内的数的个数。

我会树状数组!

O(nnlogn) ,会超时。

根号平衡

运用分块的优越性质:分块可以实现 O(1) 插入, O(n) 查询。或者 O(n) 插入 O(1) 查询。

莫队需要大量的插入操作,这使得复杂度均匀为 O(logn) 的 bit 难以承受。

这到题,对值域分块,实现 O(1) 插入 O(n) 查询,总共 O((n+m)n)

#include <bits/stdc++.h>
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(nnlogn) ,在神仙数据下只有。。20 pts

转移复杂度神仙,考虑二次离线。

剩下的就比较模板了,但是如果还用 bit 的话,大量的查询导致超时。

需要记录一个向前形成的逆序对和一个向后形成的逆序对

还记得根号平衡?用 O(n) 插入 O(1) 查询的分块就可以了。

#include <bits/stdc++.h>
#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<mdy> 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] 此时此刻的光辉

[Ynoi2015] 此时此刻的光辉

求区间内所有数乘积的约数个数 (mod19260817)

n105,ai109

用约数个数定理计算,Pollard-Rho 分解质因数后莫队可以直接算贡献。

却发现: 109 级别的质数个数,难以保证复杂度。

想到一个定理:一个数 v 只会有最多一个 v 的质因子。

预处理出 v 的质数,然后求前缀和?

又发现:109 约等于 31622 ,大约有 3401 个质数,查询和预处理都是 O(3401n) ,难以接受。

扩展一下那个定理:一个数 v 最多只会有两个 v3 的质因子。

1093=1000 ,大约只有 168 个质数, O(168n) 的预处理是可行的。

剩下的两个因子,用调用一次 PR 就可以了,不要写 dfs ,否则被卡常。

将大于 1000 的因子存下来,离散化是必然的

由于一个数剩下最多两个因子,保证莫队更新的复杂度为 O(1)1000 的因子用前缀和查询即可

卡常难度在 PR ,主要是我的 MRO(log2) 的。。。

注意预处理逆元需要预处理到 2n ,每个数会有两个 1000 的质因子

#include <bits/stdc++.h>
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] 盼君勿忘

[Ynoi2015] 盼君勿忘

莫队只是工具,以思维难度为主。

求区间内子序列分别取重后的和 (modp)

假设数 x 在区间 [l,r] 内出现 k 次,那么包含 x 的子序列有 2rl+12rl+1k

所以 x 的贡献就是 x×(2rl+12rl+1k)

si 为出现次数为 i 的和, cnti 为数 i 出现次数。

双向链表维护出现次数为 i 的数的和,不同的 i 值最多 n 个。遍历一次链表可以算出答案。

复杂度为 O((n+m)n)

光速幂

每一个询问模数不同,快速幂?平白多处一个 log ?预处理?直接 O(n)

引出黑科技:光速幂。

对于每一个询问,预处理 20,21,,2n12n,22n,,2n

求一个 2p ,用 p=kn+l(0l<n) 计算即可。

预处理 O(n) ,查询 O(1)

#include <bits/stdc++.h>
#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

『MdOI R1』Path

trie 显然。

结论:对于两条不相交的路径,存在一点 u 使得一条路径在子树 u 内,一条在子树外。

转为 dfs 序,将数组复制一份,对于每一个 u ,相当于两个询问。

由于是求 max ,用只增不减的回滚莫队即可。

O(nnlogw)

#include <bits/stdc++.h>
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);
}

本文作者:小蒟蒻laf

本文链接:https://www.cnblogs.com/KonjakLAF/p/16595178.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   小蒟蒻laf  阅读(68)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起