根号算法杂记

根号算法杂记

序列分块

某些 DS 问题中需要维护一些离散的信息单位,一般是单点修改。

  • 若维护的信息支持简单合并,则可以考虑用线段树、平衡树等区间数据结构。

  • 若并不存在高效的信息合并化简方式,则可以考虑分块。

序列分块的基本思想是通过对原序列的适当划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度。

对于某些操作问题,若无法实时维护信息,则可以维护整块必要的信息,在查询散块时暴力重构。

P5356 [Ynoi2017] 由乃打扑克

维护序列 \(a_{1 \sim n}\) ,需要支持区间加和查询区间 \(k\) 大值。

\(n, m \le 10^5\)

考虑分块,维护每个块的有序数组。

修改时整块的有序数组不变,散块暴力重构即可,该部分时间复杂度 \(O(\frac{n}{B} + B \log B)\)

查询时二分答案,则需要在 \(O(\frac{n}{B})\) 个整块上二分和 \(O(B)\) 个散块上判定,可以事先将散块的元素归并以优化复杂度,时间复杂度 \(O(\frac{n}{B} \log B \log V + B)\)

\(B = 266\) ,需要一定的常数优化即可通过。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7;

vector<pair<int, int> > vec[N];

int a[N], bel[N], L[N], R[N], tag[N];

int n, m, block, tot;

inline void prework() {
    block = 266;

    while (++tot) {
        L[tot] = R[tot - 1] + 1, R[tot] = min(block * tot, n);
        fill(bel + L[tot], bel + R[tot] + 1, tot);

        for (int i = L[tot]; i <= R[tot]; ++i)
            vec[tot].emplace_back(a[i], i);

        sort(vec[tot].begin(), vec[tot].end());

        if (R[tot] == n)
            break;
    }
}

inline void rebuild(int id, int l, int r, int k) {
    for (int i = l; i <= r; ++i)
        a[i] += k;

    vector<pair<int, int> > v1, v2;

    for (auto it : vec[id]) {
        if (l <= it.second && it.second <= r)
            v2.emplace_back(it.first + k, it.second);
        else
            v1.emplace_back(it.first, it.second);
    }

    merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vec[id].begin());
}

inline void update(int l, int r, int k) {
    if (bel[l] == bel[r]) {
        rebuild(bel[l], l, r, k);
        return;
    }

    rebuild(bel[l], l, R[bel[l]], k);

    for (int i = bel[l] + 1; i < bel[r]; ++i)
        tag[i] += k;

    rebuild(bel[r], L[bel[r]], r, k);
}

inline int query(int l, int r, int k) {
    if (k < 1 || k > r - l + 1)
        return -1;

    if (bel[l] == bel[r]) {
        vector<int> vec(r - l + 1);

        for (int i = l; i <= r; ++i)
            vec[i - l] = a[i];

        sort(vec.begin(), vec.end());
        return vec[k - 1] + tag[bel[l]];
    }

    vector<int> now;

    for (int i = l; i <= R[bel[l]]; ++i)
        now.emplace_back(a[i] + tag[bel[l]]);

    for (int i = L[bel[r]]; i <= r; ++i)
        now.emplace_back(a[i] + tag[bel[r]]);

    sort(now.begin(), now.end());

    auto getrank = [&] (int l, int r, int k) {
        int rnk = upper_bound(now.begin(), now.end(), k) - now.begin();

        for (int i = bel[l] + 1; i < bel[r]; ++i) {
            if (k - tag[i] >= vec[i].back().first)
                rnk += R[i] - L[i] + 1;
            else if (k - tag[i] >= vec[i].front().first)
                rnk += upper_bound(vec[i].begin(), vec[i].end(), make_pair(k - tag[i], n + 1)) - vec[i].begin();
        }

        return rnk;
    };

    ll L = -2e9, R = 2e9, ans = -2e9;

    while (L <= R) {
        ll mid = (L + R) >> 1;

        if (getrank(l, r, mid) >= k)
            ans = mid, R = mid - 1;
        else
            L = mid + 1;
    }

    return ans;
}

signed main() {
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);

    prework();

    while (m--) {
        int op, l, r, k;
        scanf("%d%d%d%d", &op, &l, &r, &k);

        if (op == 1)
            printf("%d\n", query(l, r, k));
        else
            update(l, r, k);
    }

    return 0;
}

P4135 作诗

查询区间内出现偶数次的数的个数,强制在线。

\(n, m \le 10^5\)

\(z_{i, x}\) 表示前 \(i\) 个块中 \(x\) 的出现次数,差分可得任意块区间中某个数的出现次数。

再记 \(s_{i, j}\) 表示第 \(i\) 块到第 \(j\) 块的合法数个数,这两者都可以 \(O(n \sqrt{n})\) 预处理。

查询时先整块直接用预处理出的信息,散块暴力枚举即可。

时间复杂度 \(O(n \sqrt{n} + m \sqrt{n})\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, B = 327;

bitset<N> vis, cnt;

int s[B][B], z[B][N];
int a[N], L[N], R[N], bel[N];

int n, c, m, block, tot;

inline void prework() {
    block = sqrt(n);

    while (++tot) {
        L[tot] = R[tot - 1] + 1, R[tot] = min(block * tot, n);
        fill(bel + L[tot], bel + R[tot] + 1, tot);
        memcpy(z[tot] + 1, z[tot - 1] + 1, sizeof(int) * c);

        for (int i = L[tot]; i <= R[tot]; ++i)
            ++z[tot][a[i]];

        if (R[tot] == n)
            break;
    }

    for (int i = 1; i <= tot; ++i) {
        int res = 0;

        for (int j = i; j <= tot; ++j) {
            for (int k = L[j]; k <= R[j]; ++k) {
                if (!vis.test(a[k]))
                    vis.set(a[k]), ++res;

                res -= !cnt.test(a[k]), cnt.flip(a[k]), res += !cnt.test(a[k]);
            }

            s[i][j] = res;
        }

        vis.reset(), cnt.reset();
    }
}

inline int query(int l, int r) {
    if (bel[l] == bel[r]) {
        int ans = 0;

        for (int i = l; i <= r; ++i) {
            if (!vis.test(a[i]))
                vis.set(a[i]), ++ans;

            ans -= !cnt.test(a[i]), cnt.flip(a[i]), ans += !cnt.test(a[i]);
        }

        for (int i = l; i <= r; ++i)
            vis.reset(a[i]), cnt.reset(a[i]);

        return ans;
    }

    int ans = s[bel[l] + 1][bel[r] - 1];

    for (int i = l; i <= R[bel[l]]; ++i) {
        if (!vis[a[i]]) {
            vis.set(a[i]);
            cnt.set(a[i], (z[bel[r] - 1][a[i]] - z[bel[l]][a[i]]) & 1);

            if (z[bel[l]][a[i]] == z[bel[r] - 1][a[i]])
                ++ans;
        }

        ans -= !cnt.test(a[i]), cnt.flip(a[i]), ans += !cnt.test(a[i]);
    }

    for (int i = L[bel[r]]; i <= r; ++i) {
        if (!vis[a[i]]) {
            vis.set(a[i]);
            cnt.set(a[i], (z[bel[r] - 1][a[i]] - z[bel[l]][a[i]]) & 1);

            if (z[bel[l]][a[i]] == z[bel[r] - 1][a[i]])
                ++ans;
        }

        ans -= !cnt.test(a[i]), cnt.flip(a[i]), ans += !cnt.test(a[i]);
    }

    for (int i = l; i <= R[bel[l]]; ++i)
        vis.reset(a[i]), cnt.reset(a[i]);

    for (int i = L[bel[r]]; i <= r; ++i)
        vis.reset(a[i]), cnt.reset(a[i]);

    return ans;
}

signed main() {
    scanf("%d%d%d", &n, &c, &m);

    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);

    prework();
    int lstans = 0;

    while (m--) {
        int l, r;
        scanf("%d%d", &l, &r);
        l = (l + lstans) % n + 1, r = (r + lstans) % n + 1;

        if (l > r)
            swap(l, r);

        printf("%d\n", lstans = query(l, r));
    }

    return 0;
}

P8527 [Ynoi2003] 樋口円香

给出序列 \(a_{1 \sim n}\)\(b_{1 \sim n}\) ,初始时 \(b_i = 0\)\(m\) 次操作,每次给出 \(l, r, L\) ,对于所有 \(k \in [l, r]\) ,将 \(b_{L + k - l}\) 增加 \(a_k \) 。最后输出 \(b_{1 \sim n}\)

\(n \le 10^5\)\(m \le 10^6\)\(0 \le a_i \le 1000\)

考虑分块,设块长为 \(B\) ,每次操作暴力处理散块,整块先存下来最后一起处理。

对于一个整块 \([l, r]\) ,记 \(c_i\) 表示这个块加到以 \(b_i\) 开始的区间的次数,\(d_i = a_{l + i} (0 \le i \le r - l)\) ,则 \(b_i \gets \sum_{j = 1}^i c_j d_{i - j}\) ,不难用 NTT 处理。注意 NTT 模数应选取为 \(1004535809\) ,因为卷积时答案上限为 \(10^9\)

时间复杂度 \(O(mB + \frac{n}{B} (n \log n + m))\) ,取 \(B = \sqrt{\frac{n^2 \log n + nm}{m}}\) 即可做到 \(O(n \sqrt{m \log n} + m \sqrt{n})\)

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7, B = 51, LOGN = 19;

int c[B][N], a[N], ans[N], L[N], R[N], bel[N];

int n, m, block, tot;

template <class T = int>
inline T read() {
    char c = getchar();
    bool sign = (c == '-');
    
    while (c < '0' || c > '9')
        c = getchar(), sign |= (c == '-');
    
    T x = 0;
    
    while ('0' <= c && c <= '9')
        x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    
    return sign ? (~x + 1) : x;
}

inline void prework() {
    block = 2048;

    while (++tot) {
        L[tot] = R[tot - 1] + 1, R[tot] = min(n, tot * block);
        fill(bel + L[tot], bel + R[tot] + 1, tot);

        if (R[tot] == n)
            break;
    }
}

namespace Poly {
#define cpy(f, g, n) memcpy(f, g, sizeof(int) * (n))
#define clr(f, n) memset(f, 0, sizeof(int) * (n))
const int Mod = 1004535809, rt = 3, invrt = (Mod + 1) / 3;

int iG[2][LOGN][N];
int inv[N], rev[N], a[N], b[N];

inline int add(int x, int y) {
    x += y;
    
    if (x >= Mod)
        x -= Mod;
    
    return x;
}

inline int dec(int x, int y) {
    x -= y;
    
    if (x < 0)
        x += Mod;
    
    return x;
}

inline int mi(int a, int b) {
    int res = 1;
    
    for (; b; b >>= 1, a = 1ll * a * a % Mod)
        if (b & 1)
            res = 1ll * res * a % Mod;
    
    return res;
}

inline void prework() {
    for (int i = 1, lgi = 0; i < N; i <<= 1, ++lgi) {
        int val[2] = {mi(invrt, (Mod - 1) / (i << 1)), mi(rt, (Mod - 1) / (i << 1))};
        iG[0][lgi][0] = iG[1][lgi][0] = 1;

        for (int j = 1; j < i; ++j) {
            iG[0][lgi][j] = 1ll * iG[0][lgi][j - 1] * val[0] % Mod;
            iG[1][lgi][j] = 1ll * iG[1][lgi][j - 1] * val[1] % Mod;
        }
    }
}

inline int calc(int n) {
    int len = 1;

    while (len < n)
        len <<= 1;

    for (int i = 0; i < len; ++i)
        rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? len >> 1 : 0);

    return len;
}

inline void NTT(int *f, int n, int op) {
    for (int i = 0; i < n; ++i)
        if (i < rev[i])
            swap(f[i], f[rev[i]]);

    for (int k = 1, lgk = 0; k < n; k <<= 1, ++lgk)
        for (int i = 0; i < n; i += k << 1)
            for (int j = 0; j < k; ++j) {
                int fl = f[i + j], fr = 1ll * iG[op == 1][lgk][j] * f[i + j + k] % Mod;
                f[i + j] = add(fl, fr), f[i + j + k] = dec(fl, fr);
            }

    if (op == -1) {
        int invn = mi(n, Mod - 2);

        for (int i = 0; i < n; ++i)
            f[i] = 1ll * f[i] * invn % Mod;
    }
}

inline void Mul(int *f, int n, int *g, int m, int *res) {
    int len = calc(n + m - 1);
    cpy(a, f, n), clr(a + n, len - n);
    cpy(b, g, m), clr(b + m, len - m);
    NTT(a, len, 1), NTT(b, len, 1);

    for (int i = 0; i < len; ++i)
        a[i] = 1ll * a[i] * b[i] % Mod;

    NTT(a, len, -1), cpy(res, a, len);
}

#undef cpy
#undef clr
} // namespace Poly

signed main() {
    n = read();

    for (int i = 1; i <= n; ++i)
        a[i] = read();

    m = read(), prework();

    while (m--) {
        int l = read(), r = read(), p = read();

        if (bel[r] <= bel[l] + 1) {
            for (int i = l; i <= r; ++i)
                ans[p + i - l] += a[i];
        } else {
            for (int i = l; i <= R[bel[l]]; ++i)
                ans[p + i - l] += a[i];

            for (int i = bel[l] + 1; i < bel[r]; ++i)
                ++c[i][p + L[i] - l];

            for (int i = L[bel[r]]; i <= r; ++i)
                ans[p + i - l] += a[i];
        }
    }

    Poly::prework();

    for (int i = 1; i <= tot; ++i) {
        Poly::Mul(c[i], n + 1, a + L[i], R[i] - L[i] + 1, c[i]);

        for (int j = 1; j <= n; ++j)
            ans[j] += c[i][j];
    }

    for (int i = 1; i <= n; ++i)
        printf("%d\n", ans[i]);

    return 0;
}

P11831 [省选联考 2025] 追忆

给出一张有向图,每条边 \((u, v)\) 均满足 \(u < v\) 。给出两个排列 \(a_{1 \sim n}\)\(b_{1 \sim n}\)\(q\) 次操作,操作有:

  • 1 x y :交换 \(a_x\)\(a_y\)
  • 2 x y :交换 \(b_x\)\(b_y\)
  • 3 x l r :求所有满足 \(x\) 可以到达 \(y\)\(a_y \in [l, r]\)\(y\)\(b_y\) 的最大值。

\(n, q \le 10^5\)\(m \le 2 \times 10^5\)

首先处理点对之间的可达性问题,由于该图是一个 DAG,因此可以采用 bitset ,每次将 \(u\)bitset 或上 \(v\)bitset 得到,时间复杂度 \(O(\frac{nm}{\omega})\)

考虑对 \(a_{1 \sim n}\) 的值域分块,维护每个 \(a\) 对应的点。设块长为 \(B\) ,每块 \([l, r]\) 开一个 bitset 维护 \(a_u \in [l, r]\) 的点,则不难做到单次 \(O(1)\) 修改和区间 \(O(\frac{n}{B} \times \frac{n}{\omega} + B)\) 查询。

考虑询问,先考虑拿出合法的点,即 \(x\) 的可达点的 bitset 与上 \(a_y \in [l, r]\)bitset 。暴力的想法是降序枚举 \(b\) ,然后每次查询是否存在合法点 \(y\) 满足 \(b_y\) 为当前枚举到的值。单次查询复杂度 \(O(n)\) ,无法接受。

一个优化想法是二分答案判定,需要区间查询 \(b\) 值域上一段后缀的对应点集。此时可以考虑套用刚才的分块结构,对 \(b\) 进行同样的分块维护,则时间复杂度降为 \(O((\frac{n}{B} \times \frac{n}{\omega} + B) \log n)\)

事实上可以去掉这个 \(\log n\) ,发现每次二分答案完全没有利用分块的性质。考虑查询时降序枚举 \(b\) 的块,若当前块内的点与合法点有交,则再暴力遍历当前块中的点找到交集中 \(b\) 最大的点即可,该部分复杂度可以做到 \(O(\frac{n}{B} \times \frac{n}{\omega} + B)\)

\(B = \frac{n}{\sqrt{\omega}}\) ,时间复杂度 \(O(\frac{nm}{\omega} + \frac{nq}{\sqrt{\omega}})\) ,由于常数小实际效率不错。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, S = 1.25e4 + 7;

struct Graph {
    vector<int> e[N];

    inline void clear(int n) {
        for (int i = 1; i <= n; ++i)
            e[i].clear();
    }

    inline void insert(int u, int v) {
        e[u].emplace_back(v);
    }
} G;

struct Node {
    int op, x, l, r;
} nd[N];

bitset<N> e[N];

int bel[N], L[N], R[N];

int n, m, q, block, tot;

inline void prework() {
    block = max(n / 8, 1), tot = 0;

    while (++tot) {
        L[tot] = R[tot - 1] + 1, R[tot] = min(block * tot, n);
        fill(bel + L[tot], bel + R[tot] + 1, tot);

        if (R[tot] == n)
            break;
    }
}

struct Solver {
    bitset<N> bit[S];

    int val[N], id[N];

    inline void insert(int x) {
        bit[bel[x]].set(id[x]);
    }

    inline void remove(int x) {
        bit[bel[x]].reset(id[x]);
    }

    inline void prework() {
        for (int i = 1; i <= tot; ++i)
            bit[i].reset();

        for (int i = 1; i <= n; ++i)
            id[val[i]] = i, insert(val[i]);
    }

    inline bitset<N> query(int l, int r) {
        bitset<N> res;
        res.reset();

        if (bel[l] == bel[r]) {
            for (int i = l; i <= r; ++i)
                res.set(id[i]);
        } else {
            for (int i = l; i <= R[bel[l]]; ++i)
                res.set(id[i]);

            for (int i = bel[l] + 1; i < bel[r]; ++i)
                res |= bit[i];

            for (int i = L[bel[r]]; i <= r; ++i)
                res.set(id[i]);
        }

        return res;
    }

    inline void update(int x, int y) {
        remove(val[x]), remove(val[y]);
        swap(val[x], val[y]), id[val[x]] = x, id[val[y]] = y;
        insert(val[x]), insert(val[y]);
    }
} A, B;

int main() {
    int testid, T;
    scanf("%d%d", &testid, &T);

    while (T--) {
        scanf("%d%d%d", &n, &m, &q);
        G.clear(n);

        for (int i = 1; i <= m; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            G.insert(u, v);
        }

        for (int u = n; u; --u) {
            e[u].reset(), e[u].set(u);

            for (int v : G.e[u])
                e[u] |= e[v];
        }

        for (int i = 1; i <= n; ++i)
            scanf("%d", &A.val[i]);

        for (int i = 1; i <= n; ++i)
            scanf("%d", &B.val[i]);

        prework(), A.prework(), B.prework();

        for (int i = 1; i <= q; ++i) {
            scanf("%d", &nd[i].op);

            if (nd[i].op == 3)
                scanf("%d%d%d", &nd[i].x, &nd[i].l, &nd[i].r);
            else
                scanf("%d%d", &nd[i].l, &nd[i].r);
        }

        for (int i = 1; i <= q; ++i) {
            if (nd[i].op == 1)
                A.update(nd[i].l, nd[i].r);
            else if (nd[i].op == 2)
                B.update(nd[i].l, nd[i].r);
            else {
                bitset<N> now = e[nd[i].x] & A.query(nd[i].l, nd[i].r);

                if (now.none()) {
                    puts("0");
                    continue;
                }

                int p = 0;

                for (int j = bel[n]; j; --j)
                    if ((B.bit[j] & now).any()) {
                        p = j;
                        break;
                    }

                for (int j = R[p]; j >= L[p]; --j)
                    if (now.test(B.id[j])) {
                        printf("%d\n", j);
                        break;
                    }
            }
        }
    }

    return 0;
}

DFS 模板题

给定一棵 \(n\) 个节点的无根树,记树上与点 \(u\) 相邻的点为 \(e_{u, 0 \sim k_u - 1}\)。同时每个节点 \(u\) 有一个初始激活编号 \(0 \le t_u < k_u\)

\(q\) 次操作,操作有:

  • C u x :修改 \(t_u\)\(x\)
  • Q x :询问初始 \(u = 1\)、进行 \(x\)\(p \gets u\)\(u \gets e_{u, t_u}\)\(t_p \gets (t_p + 1) \bmod k_p\) 操作后 \(u\) 的编号,注意询问并没有真正修改 \(t\) 数组。

\(n, q \le 10^5\) ,ML = 32 MB

不妨设原树以节点 \(1\) 为根,对于点 \(u \ne 1\) 钦定 \(e_{u, k_u - 1}\) 为其父亲,只要将出边与 \(t\) rotate 一下即可。

考虑从 \(1\) 出发到后首次返回 \(1\) 之间的一轮:

  • 把每条无向边视为两条有向边,则每条有向边至多被经过一次。

  • 在本轮被经过的有向边下一轮仍会被经过。

  • 该过程中经过的节点 \(u\) 在返回后有 \(t_u = 0\)

考虑包含 \(1\) 的满足 \(t_u = 0\) 的极大连通块,其中每个节点的儿子节点在一轮“出发-返回”后都会加入该连通块。故只要模拟 \(O(n^2)\) 步后就有 \(\forall 1 \le u \le n, t_u = 0\),此时每轮“出发-返回”相当于按欧拉序遍历每条边,不难得到 \(O(qn^2)\) 的模拟算法。

考虑维护每条有向边在第几轮“出发-返回”被第一次遍历,每次修改操作相当于将欧拉序中一个区间里的值 \(\pm 1\)

考虑分块,块长为 \(B\) ,则可以做到 \(O(B)\) 修改、\(O(\frac{n}{B})\) 回答前 \(x\) 轮“出发-返回”需要走多少步。询问时二分答案即可做到时间复杂度 \(O((n + q) (B + \frac{n}{B} \log n))\) ,取 \(B = \sqrt{n \log n}\) 即可做到 \(O((n + q) \sqrt{n \log n})\)

注意到欧拉序上相邻的边原图也相邻,故被第一次遍历到的轮数至多相差一轮,可以由此做到空间 \(O(n)\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7;

struct Graph {
    vector<int> e[N];
    
    inline void insert(int u, int v) {
        e[u].emplace_back(v);
    }
} G;

int k[N], t[N], faid[N], fa[N], seq[N], in[N], out[N], tim[N];

int n, q, tot, B;

void dfs1(int u, int f) {
    fa[u] = f;

    for (int v : G.e[u])
        if (v != f)
            dfs1(v, u);
}

void dfs2(int u, int d) {
    seq[in[u] = out[u] = ++tot] = u, tim[tot] = d;

    for (int i = 0; i < G.e[u].size() - (u != 1); ++i) {
        int v = G.e[u][i];
        dfs2(v, d + (i < t[u])), seq[out[v] = ++tot] = u, tim[tot] = d + (i < t[u]);
    }
}

namespace FK {
const int B1 = 1.1e2 + 7, B2 = 1.9e3 + 7;

ll sum[B1][B2];
int cnt[B1][B2], mn[B1], mx[B1], tag[B1];
int L[B1], R[B1], bel[N];

int B, tot;

inline void build(int x) {
    for (int i = 0; i <= mx[x] - mn[x]; ++i)
        cnt[x][i] = sum[x][i] = 0;

    mn[x] = *min_element(tim + L[x], tim + R[x] + 1), mx[x] = *max_element(tim + L[x], tim + R[x] + 1);

    for (int i = L[x]; i <= R[x]; ++i)
        ++cnt[x][tim[i] - mn[x]], sum[x][tim[i] - mn[x]] += tim[i];

    for (int i = 1; i <= mx[x] - mn[x]; ++i)
        cnt[x][i] += cnt[x][i - 1], sum[x][i] += sum[x][i - 1];
}

inline void prework() {
    B = sqrt(n * __lg(n));

    while(++tot) {
        L[tot] = R[tot - 1] + 1, R[tot] = min(n, tot * B);
        fill(bel + L[tot], bel + R[tot] + 1, tot);
        build(tot);

        if (R[tot] == n)
            break;
    }
}

inline void update(int l, int r, int k) {
    if (bel[l] == bel[r]) {
        for (int i = l; i <= r; ++i)
            tim[i] += k;

        build(bel[l]);
    } else {
        for (int i = l; i <= R[bel[l]]; ++i)
            tim[i] += k;

        build(bel[l]);

        for (int i = bel[l] + 1; i < bel[r]; ++i)
            tag[i] += k;

        for (int i = L[bel[r]]; i <= r; ++i)
            tim[i] += k;

        build(bel[r]);
    }
}

pair<int, ll> query(int k) {
    pair<int, ll> res = make_pair(0, 0ll);

    for (int i = 1; i <= tot; ++i) {
        if (k < mn[i] + tag[i])
            continue;

        res.first += cnt[i][min(k - tag[i], mx[i]) - mn[i]];
        res.second += sum[i][min(k - tag[i], mx[i]) - mn[i]] +
            1ll * tag[i] * cnt[i][min(k - tag[i], mx[i]) - mn[i]];
    }

    return res;
}

inline int find(int k, int s) {
    for (int i = 1; i <= tot; ++i) {
        if (k < mn[i] + tag[i])
            continue;

        if (s <= cnt[i][min(k - tag[i], mx[i]) - mn[i]]) {
            for (int j = L[i]; j <= R[i]; ++j) {
                s -= (tim[j] + tag[i] <= k);

                if (!s)
                    return j;
            }
        } else
            s -= cnt[i][min(k - tag[i], mx[i]) - mn[i]];
    }

    return -1;
}
} // namespace FK

signed main() {
    scanf("%d", &n);

    for (int i = 1; i <= n; ++i) {
        scanf("%d%d", k + i, t + i);
        G.e[i].resize(k[i]);

        for (int &it : G.e[i])
            scanf("%d", &it);
    }

    dfs1(1, 0);

    for (int i = 2; i <= n; ++i) {
        faid[i] = find(G.e[i].begin(), G.e[i].end(), fa[i]) - G.e[i].begin();
        t[i] = (t[i] - (faid[i] + 1) + k[i]) % k[i];

        if (faid[i] + 1 < G.e[i].size())
            rotate(G.e[i].begin(), G.e[i].begin() + faid[i] + 1, G.e[i].end());
    }

    dfs2(1, 1), n = tot - 1, FK::prework();
    scanf("%d", &q);

    while (q--) {
        char op[2];
        scanf("%s", op);

        if (op[0] == 'C') {
            int x, y;
            scanf("%d%d", &x, &y);

            if (x != 1)
                y = (y - faid[x] - 1 + k[x]) % k[x];

            if (y < t[x])
                FK::update(in[G.e[x][y]], out[G.e[x][t[x] - 1]], -1);
            else if (y > t[x])
                FK::update(in[G.e[x][t[x]]], out[G.e[x][y - 1]], 1);

            t[x] = y;
        } else {
            ll x;
            scanf("%lld", &x);
            int val = 0;

            for (int i = 0; i <= FK::bel[n]; ++i)
                val = max(val, FK::mx[i] + FK::tag[i]);

            int l = 0, r = val, pos = 0;

            while (l <= r) {
                int mid = (l + r) >> 1;
                auto res = FK::query(mid);

                if (1ll * (mid + 1) * res.first - res.second <= x)
                    pos = mid, l = mid + 1;
                else
                    r = mid - 1;
            }

            auto res = FK::query(pos);
            x -= 1ll * (pos + 1) * res.first - res.second;
            printf("%d\n", pos == val ? seq[x % n + 1] : seq[FK::find(pos + 1, x + 1)]);
        }
    }

    return 0;
}

QOJ10723. Outer LIS

给定序列 \(a_{1 \sim n}\) ,每个位置有权值 \(w_{1 \sim n}\)\(q\) 次询问,每次给出区间 \([l, r]\) ,求不选 \([l, r]\) 下标内元素的所有 \(a\) 的不降子序列中权值和的最大值(带权 LIS)。

\(n, q \le 10^5\)

首先预处理 \(f_i, g_i\) 表示以 \(i\) 结尾/开头的带权 LIS,对于每个询问,答案可能来自于:

  • \(f\) 的前缀最大值或 \(g\) 的后缀最大值。
  • 选取 \(i \in [1, l - 1], j \in [r + 1, n]\) 满足 \(a_i \le a_j\) ,贡献为 \(f_i + g_j\)

前者是容易的,考虑后者。将序列按块长为 \(\sqrt{n}\) 分块,记总块数为 \(m\) ,预处理:

  • \(pre_{i, j}\) :第 \([1, i]\) 块中满足 \(a \le j\)\(\max f\)
  • \(suf_{i, j}\) :第 \([i, m]\) 块中满足 \(a \ge j\)\(\max g\)
  • \(ans_{i, j}\) :第 \([1, i]\) 块与第 \([j, m]\) 块元素两两组合的最优解。

以上信息不难 \(O(n \sqrt{n})\) 预处理。

对于询问 \([l, r]\) ,记左右端点所在块为 \(x, y\) ,答案可能来自于以下部分:

  • \([1, x - 1]\) 块与第 \([y + 1, m]\) 块的元素拼接,即 \(ans_{x - 1, y + 1}\)
  • \(x\) 块中下标 \(< l\) 的零散元素与第 \([y + 1, m]\) 块的元素拼接,枚举零散元素后利用 \(suf\) 数组即可查询。
  • \(y\) 块中下标 \(> r\) 的零散元素与第 \([1, x - 1]\) 块的元素拼接,枚举零散元素后利用 \(pre\) 数组即可查询。
  • 两块的零散元素两两组合,排序后双指针计算。在预处理阶段对每个块按 \(a\) 进行排序,双指针时直接筛掉不在范围内的元素即可不带 \(\log\)

时间复杂度 \(O((n + q) \sqrt{n})\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7, B = 327;

vector<int> vec[B];

ll f[N], g[N], pref[N], sufg[N], pre[B][N], suf[B][N], ans[B][B];
int a[N], val[N], L[B], R[B], bel[N];

int n, q, block, tot;

namespace BIT {
ll c[N];

inline void update(int x, ll k) {
    for (; x <= n; x += x & -x)
        c[x] = max(c[x], k);
}

inline ll query(int x) {
    ll res = 0;

    for (; x; x -= x & -x)
        res = max(res, c[x]);

    return res;
}
} // namespace BIT

inline void prework() {
    block = sqrt(n);

    while (++tot) {
        L[tot] = R[tot - 1] + 1, R[tot] = min(n, tot * block);
        fill(bel + L[tot], bel + R[tot] + 1, tot);

        for (int i = L[tot]; i <= R[tot]; ++i) {
            pre[tot][a[i]] = max(pre[tot][a[i]], f[i]);
            suf[tot][a[i]] = max(suf[tot][a[i]], g[i]);
        }

        for (int i = 2; i <= n; ++i)
            pre[tot][i] = max(pre[tot][i], pre[tot][i - 1]);

        for (int i = n - 1; i; --i)
            suf[tot][i] = max(suf[tot][i], suf[tot][i + 1]);

        vec[tot].resize(R[tot] - L[tot] + 1), iota(vec[tot].begin(), vec[tot].end(), L[tot]);

        sort(vec[tot].begin(), vec[tot].end(), [](const int &x, const int &y) {
            return a[x] < a[y];
        });

        if (R[tot] == n)
            break;
    }

    for (int i = 2; i <= tot; ++i)
        for (int j = 1; j <= n; ++j)
            pre[i][j] = max(pre[i][j], pre[i - 1][j]);

    for (int i = tot - 1; ~i; --i)
        for (int j = 1; j <= n; ++j)
            suf[i][j] = max(suf[i][j], suf[i + 1][j]);

    for (int i = 1; i <= tot; ++i) {
        memcpy(ans[i] + 1, ans[i - 1] + 1, sizeof(ll) * tot);

        for (int j = i + 1; j <= tot; ++j)
            for (int k = L[i]; k <= R[i]; ++k)
                ans[i][j] = max(ans[i][j], f[k] + suf[j][a[k]]);
    }
}

inline ll query(int l, int r) {
    int x = bel[l], y = bel[r];
    ll res = max(max(pref[l - 1], sufg[r + 1]), ans[x - 1][y + 1]);

    for (int i = L[x]; i < l; ++i)
        res = max(res, f[i] + suf[y + 1][a[i]]);

    for (int i = r + 1; i <= R[y]; ++i)
        res = max(res, pre[x - 1][a[i]] + g[i]);

    ll mx = 0;

    for (int i = 0, j = 0; i < vec[y].size(); ++i) {
        while (j < vec[x].size() && a[vec[x][j]] <= a[vec[y][i]])
            mx = max(mx, vec[x][j] < l ? f[vec[x][j]] : 0), ++j;

        if (vec[y][i] > r)
            res = max(res, mx + g[vec[y][i]]);
    }

    return res;
}

signed main() {
    scanf("%d%d", &n, &q);

    for (int i = 1; i <= n; ++i)
        scanf("%d%d", a + i, val + i);

    for (int i = 1; i <= n; ++i)
        BIT::update(a[i], f[i] = BIT::query(a[i]) + val[i]), pref[i] = max(pref[i - 1], f[i]);

    memset(BIT::c + 1, 0, sizeof(ll) * n);

    for (int i = n; i; --i)
        BIT::update(n - a[i] + 1, g[i] = BIT::query(n - a[i] + 1) + val[i]), sufg[i] = max(sufg[i + 1], g[i]);

    prework();

    while (q--) {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%lld\n", query(l, r));
    }

    return 0;
}

AT_joisc2016_h 回転寿司

给定一个 \(n\) 个点的环,第 \(i\) 个点的权值为 \(a_i\)

\(q\) 次询问,每次给出 \(s, t, k\) ,依次枚举 \(i = s \sim t\) ,若 \(a_i > k\) 则交换二者,求最终 \(k\) 的值,注意询问的影响会被保留。

\(n \le 4 \times 10^5\)\(q \le 2.5 \times 10^4\)

考虑分块,设块长为 \(B\)

显然一个 \(k\) 经过一整块后会变成 \(k\) 与整块的最大值,若只考虑整块,则每个位置的具体值是无需关心的,只要用一个堆维护最大值即可,该部分时间复杂度 \(O(q \frac{n}{B} \log B)\)

对于散块的修改是简单的,直接暴力做就好了,该部分时间复杂度 \(O(qB)\) 。但是由于之前查询整块时没有维护每个位置的值,无法直接查询,因此需要考虑求出每个位置的值。

考虑对每个块维护新插入的数字,每次直接对这个块重构。但是直接重构复杂度是 \(O(q B^2)\) 的,手动模拟可以发现插入的数的顺序对序列的值不影响,因此考虑按数字大小插入,从左到右枚举块上的所有数,如果这个数大于当前最小值,就做一次交换操作,该部分时间复杂度 \(O(q B \log q)\)

\(B = \sqrt{n}\) ,时间复杂度 \(O(q \sqrt{n} \log q)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 4e5 + 7, B = 637;

priority_queue<int> q1[B];
priority_queue<int, vector<int>, greater<int> > q2[B];

int a[N], L[N], R[N], bel[N];

int n, m, block, tot;

inline void prework() {
    block = sqrt(n);

    while (++tot) {
        L[tot] = R[tot - 1] + 1, R[tot] = min(tot * block, n);

        for (int i = L[tot]; i <= R[tot]; ++i)
            bel[i] = tot, q1[tot].emplace(a[i]);

        if (R[tot] == n)
            break;
    }
}

inline void rebuild(int x) {
    if (q2[x].empty())
        return;

    for (int i = L[x]; i <= R[x]; ++i)
        if (a[i] > q2[x].top()) {
            int k = q2[x].top();
            q2[x].pop(), q2[x].emplace(a[i]), a[i] = k;
        }

    while (!q2[x].empty())
        q2[x].pop();
}

inline int BruteForce(int x, int l, int r, int k) {
    for (int i = l; i <= r; ++i)
        if (a[i] > k)
            swap(k, a[i]);

    while (!q1[x].empty())
        q1[x].pop();

    for (int i = L[x]; i <= R[x]; ++i)
        q1[x].emplace(a[i]);

    return k;
}

inline int query(int l, int r, int k) {
    if (bel[l] == bel[r])
        return rebuild(bel[l]), BruteForce(bel[l], l, r, k);
    else {
        rebuild(bel[l]), k = BruteForce(bel[l], l, R[bel[l]], k);

        for (int i = bel[l] + 1; i < bel[r]; ++i) {
            int x = q1[i].top();

            if (x > k)
                q1[i].pop(), q1[i].emplace(k), q2[i].emplace(k), k = x;
        }

        return rebuild(bel[r]), BruteForce(bel[r], L[bel[r]], r, k);
    }
}

signed main() {
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);

    prework();

    while (m--) {
        int l, r, k;
        scanf("%d%d%d", &l, &r, &k);
        printf("%d\n", l <= r ? query(l, r, k) : query(1, r, query(l, n, k)));
    }

    return 0;
}

撒关键点

P11587 [KTSC 2022 R2] 编程测试

给定 \(a_{1 \sim n}\)\(b_{1 \sim n - 1}\) ,每个 \(b_i\) 可以选择 \(x \in [0, b_i]\) ,然后令 \(a_i \to a_i + x\)\(a_{i + 1} \to a_{i + 1} + b_i - x\)\(q\) 次询问 \(\min_{i = l}^r a_i\) 可能的最大值。

\(n, q \le 10^5\)

考虑二分答案 \(k\) ,问题可以转化为二分图匹配问题,考虑 Hall 定理,要求 \(\forall (x, y], k (y - x) \ge b_x + \sum_{i = x + 1}^y a_i + b_i\)

那么无需二分答案,答案即为 \(\min \lfloor \frac{(A_y + B_y) - (A_x + B_{x - 1})}{y - x} \rfloor\) ,其中 \(A, B\) 为前缀和。

不难发现固定 \(y\) 时最优的 \(x\) 一定在下凸壳上,直接维护下凸壳,每次二分即可。

接下来考虑多组询问,考虑分块,每隔 \(\sqrt{n}\) 放一个关键点,然后预处理每个关键点所有前后缀的答案。对于一组询问 \(l, r\) ,则还需处理左右散块之间的贡献,直接暴力即可。时间复杂度 \(O(q \sqrt{n} \log n)\)

考虑去掉 \(\log\) ,首先将这个斜率理解为平均值,假设 \(x_2 < x_1 < y_1 < y_2\)\(y_1\) 的决策点在 \(x_1\)\(y_2\) 的决策点在 \(x_2\) ,则:

  • \(f(x_1, y_1) < f(x_2, y_1)\) :这说明 \([x_2, x_1)\) 的平均数大于 \([x_1, y_1]\) 的平均数。
  • \(f(x_2, y_2) < f(x_1, y_2)\) :这说明 \([x_2, x_1)\) 的平均数小于 \([x_1, y_2]\) 的平均数。

因此 \([x_1, y_1]\) 的平均数小于 \([x_1, y_2]\) 的平均数,此时 \(f(x_2, y_2) > f(x_1, y_1)\) ,也就是说可以不用考虑 \(y_2\) 的决策,因此只要考虑有决策单调性的 \(y\) 的决策即可。

时间复杂度 \(O(n \sqrt{n})\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 7, M = 317;

pair<int, ll> q[N];

ll A[N], B[N];
int f[M][N];

int n, m, block, head, tail;

inline bool check(pair<int, ll> a, pair<int, ll> b, pair<int, ll> c) {
    return (c.second - a.second) * (c.first - b.first) <= 1ll * (c.second - b.second) * (c.first - a.first);
}

inline void insert(pair<int, ll> p) {
    while (head < tail && check(q[tail - 1], q[tail], p))
        --tail;

    q[++tail] = p;
}

inline int query(pair<int, ll> p) {
    if (head > tail)
        return inf;

    while (head < tail && check(q[head + 1], q[head], p))
        ++head;

    return (p.second - q[head].second) / (p.first - q[head].first);
}

inline void prework(int x, int *f) {
    head = 1, tail = 0, insert(make_pair(x, B[x])), f[x] = inf;

    for (int i = x + 1; i <= n; ++i)
        f[i] = min(f[i - 1], query(make_pair(i, A[i]))), insert(make_pair(i, B[i]));

    head = 1, tail = 0, insert(make_pair(-x, -A[x]));

    for (int i = x - 1; ~i; --i)
        f[i] = min(f[i + 1], query(make_pair(-i, -B[i]))), insert(make_pair(-i, -A[i]));
}

inline int solve(int l, int r) {
    if (l / block == r / block) {
        int ans = inf;
        head = 1, tail = 0;

        for (int i = l; i <= r; ++i)
            ans = min(ans, query(make_pair(i, A[i]))), insert(make_pair(i, B[i]));

        return ans;
    }

    int ans = min(f[l / block + 1][r], f[r / block][l]);
    head = 1, tail = 0;

    for (int i = l; i < (l / block + 1) * block; ++i)
        ans = min(ans, query(make_pair(i, A[i]))), insert(make_pair(i, B[i]));

    for (int i = r / block * block + 1; i <= r; ++i)
        ans = min(ans, query(make_pair(i, A[i]))), insert(make_pair(i, B[i]));

    return ans;
}

vector<int> testset(vector<int> a, vector<int> b, vector<int> ql, vector<int> qr) {
    n = a.size(), m = ql.size(), block = sqrt(n);

    for (int i = 1; i <= n; ++i) {
        A[i] = A[i - 1] + a[i - 1] + (i == n ? 0 : b[i - 1]);
        B[i] = B[i - 1] + a[i - 1] + (i == 1 ? 0 : b[i - 2]);
    }

    for (int i = 0; i * block <= n; ++i)
        prework(i * block, f[i]);

    vector<int> ans;

    for (int i = 0; i < m; ++i)
        ans.emplace_back(solve(ql[i], qr[i] + 1));

    return ans;
}

操作分块

将操作每 \(B\) 块分一组:

  • 对于块内修改涉及到的点,其对查询的贡献暴力统计。
  • 对于其他点,用 DS 每次暴力预处理,查询时整体对 DS 查询。

最后做组内的修改即可。

P5443 [APIO2019] 桥梁

给定一张无向图,每条边有一个重量限制。\(q\) 次操作,操作有:

  • 修改一条边的重量限制。
  • 查询从某点出发,只经过重量限制 \(\ge w\) 的边能到达的点的数量。

\(n \le 5 \times 10^4\)\(m, q \le 10^5\)

考虑操作分块,先对询问按 \(w\) 排序。对于修改未涉及的边按重量限制排序,用并查集维护连通性,则枚举询问时是一个不断加入边的过程。对于修改涉及到的边,每次暴力判定是否合法,若合法则加入,处理完当前询问后撤销即可。

时间复杂度 \(O(\frac{q}{B} (m \log m + (m + B^2) \log n))\)\(B\)\(\sqrt{m \log n}\) 比较快。

#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 7, M = 1e5 + 7;

struct Edge {
    int u, v, w;
} e[M];

struct Node {
    int t, x, k;
};

struct DSU {
    int fa[N], siz[N], sta[N];

    int top;

    inline void prework(int n) {
        iota(fa + 1, fa + n + 1, 1);
        fill(siz + 1, siz + n + 1, 1);
        top = 0;
    }

    inline int find(int x) {
        while (x != fa[x])
            x = fa[x];

        return x;
    }

    inline void merge(int x, int y) {
        x = find(x), y = find(y);

        if (x == y)
            return;

        if (siz[x] < siz[y])
            swap(x, y);

        siz[x] += siz[y], fa[y] = x, sta[++top] = y;
    }

    inline void restore(int k) {
        while (top > k) {
            int x = sta[top--];
            siz[fa[x]] -= siz[x], fa[x] = x;
        }
    }
} dsu;

int flag[M];

int n, m, q;

template <class T = int>
inline T read() {
    char c = getchar();
    bool sign = (c == '-');
    
    while (c < '0' || c > '9')
        c = getchar(), sign |= (c == '-');
    
    T x = 0;
    
    while ('0' <= c && c <= '9')
        x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    
    return sign ? (~x + 1) : x;
}

signed main() {
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= m; ++i)
        scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);

    scanf("%d", &q);

    int block = sqrt((m + 1) * __lg(n));
    dsu.prework(n);

    for (int qL = 1, qR; qL <= q; qL = qR + 1) {
        qR = min(qL + block - 1, q);
        vector<Node> upd, qry, ans;

        for (int i = qL; i <= qR; ++i) {
            int op, x, k;
            scanf("%d%d%d", &op, &x, &k);

            if (op == 1)
                upd.emplace_back((Node){i, x, k}), flag[x] = e[x].w;
            else
                qry.emplace_back((Node){i, x, k});
        }

        sort(qry.begin(), qry.end(), [](const Node &a, const Node &b) {
            return a.k > b.k;
        });

        vector<int> id(m);
        iota(id.begin(), id.end(), 1);

        sort(id.begin(), id.end(), [](const int &a, const int &b) {
            return e[a].w > e[b].w;
        });

        auto pos = id.begin();

        for (Node it : qry) {
            for (; pos != id.end() && e[*pos].w >= it.k; ++pos)
                if (!flag[*pos])
                    dsu.merge(e[*pos].u, e[*pos].v);

            for (Node it2 : upd) {
                if (it2.t <= it.t)
                    flag[it2.x] = it2.k;
                else
                    break;
            }

            int lsttop = dsu.top;

            for (Node it2 : upd)
                if (flag[it2.x] >= it.k)
                    dsu.merge(e[it2.x].u, e[it2.x].v);

            ans.emplace_back((Node) {it.t, 0, dsu.siz[dsu.find(it.x)]});
            dsu.restore(lsttop);

            for (Node it2 : upd)
                flag[it2.x] = e[it2.x].w;
        }

        sort(ans.begin(), ans.end(), [](const Node &a, const Node &b) { return a.t < b.t; });

        for (Node it : ans)
            printf("%d\n", it.k);

        for (Node it : upd)
            e[it.x].w = it.k, flag[it.x] = 0;

        dsu.restore(0);
    }

    return 0;
}

P11831 [省选联考 2025] 追忆

给出一张有向图,每条边 \((u, v)\) 均满足 \(u < v\) 。给出两个排列 \(a_{1 \sim n}\)\(b_{1 \sim n}\)\(q\) 次操作,操作有:

  • 1 x y :交换 \(a_x\)\(a_y\)
  • 2 x y :交换 \(b_x\)\(b_y\)
  • 3 x l r :求所有满足 \(x\) 可以到达 \(y\)\(a_y \in [l, r]\)\(y\)\(b_y\) 的最大值。

\(n, q \le 10^5\)\(m \le 2 \times 10^5\)

首先处理点对之间的可达性问题,由于该图是一个 DAG,因此可以采用 bitset ,每次将 \(u\)bitset 或上 \(v\)bitset 得到,时间复杂度 \(O(\frac{nm}{\omega})\)

发现要维护的东西很难用 DS 维护,考虑操作分块,每 \(\omega\) 个操作分一组。在一组中将 \(y\) 按该组内修改是否涉及 \(y\) 分类。被修改的 \(y\) 可以暴力枚举计算,下面考虑未被修改的 \(y\) 的信息统计。

对于每个点 \(u\) ,设 \(f_{u, i}\) 表示第 \(i\) 个询问的 \(x\) 是否可以到达 \(u\) ,这不难单组 \(O(m)\) 预处理出。

再设 \(g_{u, i}\) 表示 \(u\) 是否在第 \(i\) 个询问的 \([l, r]\) 限制内,这不难单组差分 \(O(n)\) 求出。那么对于一个 \(u\) ,其只会对 \(f_u \cap g_{a_u}\) 的询问产生贡献。

\(b\) 降序枚举 \(u\) ,每次只要将 \(f_u \cap g_{a_u}\) 中未被赋上答案的询问赋上答案 \(b_u\) 即可。

总时间复杂度 \(O(\frac{nm + mq + nq}{\omega})\) ,实测块长设为 256 时可以通过。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, B = 256;

struct Graph {
    vector<int> e[N];

    inline void clear(int n) {
        for (int i = 1; i <= n; ++i)
            e[i].clear();
    }
    
    inline void insert(int u, int v) {
        e[u].emplace_back(v);
    }
} G;

struct Node {
    int op, x, l, r;
} nd[N];

bitset<N> reach[N];
bitset<B> f[N], g[N];

int a[N], b[N], id[N], ans[N];
bool upd[N];

int n, m, q;

signed main() {
    int testid, T;
    scanf("%d%d", &testid, &T);

    while (T--) {
        scanf("%d%d%d", &n, &m, &q);
        G.clear(n);

        for (int i = 1; i <= m; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            G.insert(u, v);
        }

        for (int u = n; u; --u) {
            reach[u].reset(), reach[u].set(u);

            for (int v : G.e[u])
                reach[u] |= reach[v];
        }

        for (int i = 1; i <= n; ++i)
            scanf("%d", a + i);

        for (int i = 1; i <= n; ++i)
            scanf("%d", b + i);

        for (int i = 1; i <= q; ++i) {
            scanf("%d", &nd[i].op);

            if (nd[i].op == 1 || nd[i].op == 2)
                scanf("%d%d", &nd[i].l, &nd[i].r);
            else
                scanf("%d%d%d", &nd[i].x, &nd[i].l, &nd[i].r);
        }

        memset(ans + 1, 0, sizeof(int) * q);

        for (int L = 1, R; L <= q; L = R + 1) {
            R = min(L + B - 1, q);

            for (int i = L; i <= R; ++i)
                if (nd[i].op == 1 || nd[i].op == 2)
                    upd[nd[i].l] = upd[nd[i].r] = true;

            vector<int> vec;

            for (int u = 1; u <= n; ++u)
                if (upd[u])
                    vec.emplace_back(u);

            for (int i = 1; i <= n; ++i)
                f[i].reset(), g[i].reset();

            bitset<B> all = 0;

            for (int i = L; i <= R; ++i)
                if (nd[i].op == 3)
                    all.set(i - L), f[nd[i].x].set(i - L), g[nd[i].l].set(i - L), g[nd[i].r + 1].set(i - L);

            for (int u = 1; u <= n; ++u) {
                g[u] ^= g[u - 1], id[b[u]] = u;

                for (int v : G.e[u])
                    f[v] |= f[u];
            }

            for (int i = n; i && all.any(); --i) {
                int u = id[i];

                if (upd[u])
                    continue;

                bitset<B> now = all & f[u] & g[a[u]];
                all ^= now;

                for (int i = now._Find_first(); i != now.size(); i = now._Find_next(i))
                    ans[L + i] = b[u];
            }

            for (int i = L; i <= R; ++i) {
                if (nd[i].op == 1)
                    swap(a[nd[i].l], a[nd[i].r]);
                else if (nd[i].op == 2)
                    swap(b[nd[i].l], b[nd[i].r]);
                else {
                    for (int u : vec)
                        if (reach[nd[i].x].test(u) && nd[i].l <= a[u] && a[u] <= nd[i].r)
                            ans[i] = max(ans[i], b[u]);
                }
            }

            for (int i = L; i <= R; ++i)
                if (nd[i].op == 1 || nd[i].op == 2)
                    upd[nd[i].l] = upd[nd[i].r] = false;
        }

        for (int i = 1; i <= q; ++i)
            if (nd[i].op == 3)
                printf("%d\n", ans[i]);
    }

    return 0;
}

事实上有一种更为优秀的操作分块方法,设块长为 \(S\) 。同样对于修改涉及到的点暴力贡献答案,考虑维护其他点。

考虑对 \(a\) 值域分块,维护每个值的对应点,块长为 \(B\) 。设 \(f_{x, i}\) 表示 \(x\) 的可达点 \(y\) 中满足 \(a_y\) 属于第 \(i\) 块的 \(y\)\(b_y\) 的最大值,不难单组 \(O(\frac{nm}{B})\) 预处理。

查询时整块可以直接贡献,散块直接暴力贡献即可,单次查询复杂度 \(O(\frac{n}{B} + B)\)

时间复杂度 \(O(\frac{nm}{\omega} + \frac{q}{S} \times \frac{nm}{B} + q (S + \frac{n}{B} + B))\) ,取 \(S = B = n^{\frac{2}{3}}\) 可以做到 \(O(\frac{nm}{\omega} + n^{\frac{5}{3}})\)

#include <bits/stdc++.h>
typedef unsigned long long ull;
using namespace std;
const int N = 1e5 + 7, B = 5e1 + 7;

struct Graph {
    vector<int> e[N];

    inline void clear(int n) {
        for (int i = 1; i <= n; ++i)
            e[i].clear();
    }
    
    inline void insert(int u, int v) {
        e[u].emplace_back(v);
    }
} G;

struct Node {
    int op, x, l, r;
} nd[N];

bitset<N> reach[N];

int f[N][B];
int a[N], b[N], ans[N], L[N], R[N], bel[N], id[N];
bool upd[N];

int n, m, q, block, tot;

inline void prework() {
    block = pow(n, 0.667), tot = 0;

    while (++tot) {
        L[tot] = R[tot - 1] + 1, R[tot] = min(n, block * tot);
        fill(bel + L[tot], bel + R[tot] + 1, tot);

        if (R[tot] == n)
            break;
    }
}

signed main() {
    int testid, T;
    scanf("%d%d", &testid, &T);

    while (T--) {
        scanf("%d%d%d", &n, &m, &q);
        G.clear(n);

        for (int i = 1; i <= m; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            G.insert(u, v);
        }

        for (int u = n; u; --u) {
            reach[u].reset(), reach[u].set(u);

            for (int v : G.e[u])
                reach[u] |= reach[v];
        }

        for (int i = 1; i <= n; ++i)
            scanf("%d", a + i);

        for (int i = 1; i <= n; ++i)
            scanf("%d", b + i);

        for (int i = 1; i <= q; ++i) {
            scanf("%d", &nd[i].op);

            if (nd[i].op == 1 || nd[i].op == 2)
                scanf("%d%d", &nd[i].l, &nd[i].r);
            else
                scanf("%d%d%d", &nd[i].x, &nd[i].l, &nd[i].r);
        }

        memset(ans + 1, 0, sizeof(int) * q);
        prework();

        for (int qL = 1, qR; qL <= q; qL = qR + 1) {
            qR = min(qL + block - 1, q);

            for (int i = qL; i <= qR; ++i)
                if (nd[i].op == 1 || nd[i].op == 2)
                    upd[nd[i].l] = upd[nd[i].r] = true;

            vector<int> vec;

            for (int u = 1; u <= n; ++u)
                if (upd[u])
                    vec.emplace_back(u);

            for (int u = n; u; --u) {
                memset(f[u] + 1, 0, sizeof(int) * tot);

                if (!upd[u])
                    f[u][bel[a[u]]] = b[u];

                for (int v : G.e[u])
                    for (int i = 1; i <= tot; ++i)
                        f[u][i] = max(f[u][i], f[v][i]);
            }

            memset(id + 1, 0, sizeof(int) * n);

            for (int u = 1; u <= n; ++u)
                if (!upd[u])
                    id[a[u]] = u;


            for (int i = qL; i <= qR; ++i) {
                if (nd[i].op == 1)
                    swap(a[nd[i].l], a[nd[i].r]);
                else if (nd[i].op == 2)
                    swap(b[nd[i].l], b[nd[i].r]);
                else {
                    if (bel[nd[i].r] <= bel[nd[i].l] + 1) {
                        for (int j = nd[i].l; j <= nd[i].r; ++j)
                            if (id[j] && reach[nd[i].x].test(id[j]))
                                ans[i] = max(ans[i], b[id[j]]);
                    } else {
                        for (int j = nd[i].l; j <= R[bel[nd[i].l]]; ++j)
                            if (id[j] && reach[nd[i].x].test(id[j]))
                                ans[i] = max(ans[i], b[id[j]]);

                        for (int j = bel[nd[i].l] + 1; j < bel[nd[i].r]; ++j)
                            ans[i] = max(ans[i], f[nd[i].x][j]);

                        for (int j = L[bel[nd[i].r]]; j <= nd[i].r; ++j)
                            if (id[j] && reach[nd[i].x].test(id[j]))
                                ans[i] = max(ans[i], b[id[j]]);
                    }

                    for (int u : vec)
                        if (reach[nd[i].x].test(u) && nd[i].l <= a[u] && a[u] <= nd[i].r)
                            ans[i] = max(ans[i], b[u]);
                }
            }

            for (int i = qL; i <= qR; ++i)
                if (nd[i].op == 1 || nd[i].op == 2)
                    upd[nd[i].l] = upd[nd[i].r] = false;
        }

        for (int i = 1; i <= q; ++i)
            if (nd[i].op == 3)
                printf("%d\n", ans[i]);
    }

    return 0;
}

P11369 [Ynoi2024] 弥留之国的爱丽丝

给出一个有向图,每条边颜色为黑色或白色,最初所有边都是黑色。

\(q\) 次操作,操作有:

  • 反转某条边的颜色。
  • 查询 \(u\) 是否能只经过黑色边到达 \(v\)

\(n \le 5 \times 10^4\)\(m, q \le 10^5\)

考虑操作分块,设快长为 \(B\) 。设块内涉及到的点和边称为关键点、关键边,二者均为 \(O(B)\) 级别。

考虑先去掉关键边,对关键点预处理出两两之间可达性,将图缩为一个仅含有关键点的图。由于图不是 DAG,因此需要 Tarjan 缩点后按反拓扑序处理,用 bitset 优化处理出 \(g_{i, j}\) 表示关键点 \(i\) 是否能到达关键点 \(j\) ,该部分复杂度 \(O(n + m + \frac{mB}{\omega} + B^2)\)

问题转化为动态加边、查询可达性。动态加边直接加,查询可达性就用 bitset 优化 bfs(维护还没搜到的可达点),单次询问可以做到 \(O(\frac{B^2}{\omega})\)

时间复杂度 \(O(\frac{q}{B} \times (n + m + \frac{mB}{\omega} + B^2) + q \frac{B^2}{\omega})\) ,取 \(B = 128\) 即可通过。

#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 7, M = 1e5 + 7, B = 128, S = B << 1;

struct Graph {
    vector<int> e[N];

    inline void clear(int n) {
        for (int i = 1; i <= n; ++i)
            e[i].clear();
    }

    inline void insert(int u, int v) {
        e[u].emplace_back(v);
    }
} G, nG;

struct Edge {
    int u, v, c;
} e[M];

struct Node {
    int op, x, y;
} nd[M];

bitset<S> f[N], g[N];

int id[N], dfn[N], low[N], sta[N], leader[N];
bool vis[M];

int n, m, q, dfstime, top, scc;

void Tarjan(int u) {
    dfn[u] = low[u] = ++dfstime, sta[++top] = u;

    for (int v : G.e[u]) {
        if (!dfn[v])
            Tarjan(v), low[u] = min(low[u], low[v]);
        else if (!leader[v])
            low[u] = min(low[u], dfn[v]);
    }

    if (low[u] == dfn[u]) {
        f[++scc].reset();

        while (sta[top] != u)
            leader[sta[top--]] = scc;
        
        leader[sta[top--]] = scc;
    }
}

inline bool query(int u, int v) {
    bitset<S> reach, vis;
    reach.set(u), vis.set();

    while (!reach.test(v)) {
        int x = (reach & vis)._Find_first();

        if (x == vis.size())
            return false;

        vis.reset(x), reach |= g[x];
    }

    return true;
}

signed main() {
    scanf("%d%d%d", &n, &m, &q);

    for (int i = 1; i <= m; ++i)
        scanf("%d%d", &e[i].u, &e[i].v), e[i].c = 1;
    
    for (int i = 1; i <= q; ++i) {
        scanf("%d%d", &nd[i].op, &nd[i].x);

        if (nd[i].op == 2)
            scanf("%d", &nd[i].y);
    }

    for (int L = 1, R; L <= q; L = R + 1) {
        R = min(L + B - 1, q);
        memset(id + 1, -1, sizeof(int) * n);
        memset(vis + 1, false, sizeof(bool) * m);
        vector<int> key, edg;

        for (int i = L; i <= R; ++i) {
            if (nd[i].op == 1) {
                if (!vis[nd[i].x])
                    edg.emplace_back(nd[i].x), vis[nd[i].x] = true;

                if (id[e[nd[i].x].u] == -1)
                    id[e[nd[i].x].u] = key.size(), key.emplace_back(e[nd[i].x].u);

                if (id[e[nd[i].x].v] == -1)
                    id[e[nd[i].x].v] = key.size(), key.emplace_back(e[nd[i].x].v);
            } else {
                if (id[nd[i].x] == -1)
                    id[nd[i].x] = key.size(), key.emplace_back(nd[i].x);

                if (id[nd[i].y] == -1)
                    id[nd[i].y] = key.size(), key.emplace_back(nd[i].y);
            }
        }

        G.clear(n);

        for (int i = 1; i <= m; ++i)
            if (e[i].c && !vis[i])
                G.insert(e[i].u, e[i].v);

        memset(dfn + 1, 0, sizeof(int) * n);
        memset(low + 1, 0, sizeof(int) * n);
        memset(leader + 1, 0, sizeof(int) * n);
        dfstime = scc = 0;

        for (int i = 1; i <= n; ++i)
            if (!dfn[i])
                Tarjan(i);

        nG.clear(scc);

        for (int u = 1; u <= n; ++u)
            for (int v : G.e[u])
                if (leader[u] != leader[v])
                    nG.insert(leader[u], leader[v]);

        for (int i = 1; i <= n; ++i)
            if (~id[i])
                f[leader[i]].set(id[i]);

        for (int u = 1; u <= scc; ++u)
            for (int v : nG.e[u])
                f[u] |= f[v];

        for (int i = 0; i < key.size(); ++i) {
            g[i].reset();

            for (int j = 0; j < key.size(); ++j)
                g[i].set(j, f[leader[key[i]]].test(j));
        }

        for (int i = L; i <= R; ++i) {
            if (nd[i].op == 1)
                e[nd[i].x].c ^= 1;
            else {
                vector<int> vec;

                for (int it : edg)
                    if (e[it].c && !g[id[e[it].u]].test(id[e[it].v]))
                        vec.emplace_back(it), g[id[e[it].u]].set(id[e[it].v]);

                puts(query(id[nd[i].x], id[nd[i].y]) ? "YES" : "NO");

                for (int it : vec)
                    g[id[e[it].u]].reset(id[e[it].v]);
            }
        }
    }

    return 0;
}

根号平衡

P3396 哈希冲突

给定 \(a_{1 \sim n}\)\(m\) 次操作,操作有:

  • 求所有模 \(y\)\(x\) 的下标的数字和。
  • 单点修改。

\(n, m \le 1.5 \times 10^5\)

考虑对模数根号分治:

  • 对于 \(\le \sqrt{n}\) 的模数:修改时暴力把每个模数的答案修改过去,查询就直接查。
  • 对于 \(> \sqrt{n}\) 的模数:修改时直接在上原序列改,查询时直接暴力跳下标。

时间复杂度 \(O(m \sqrt{n})\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1.5e5 + 7, B = 4e2 + 7;

int a[N], s[B][B];

int n, m;

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j < B; ++j)
            s[j][i % j] += a[i];
    
    while (m--) {
        char op;
        int x, y;
        cin >> op >> x >> y;
        
        if (op == 'A') {
            if (x < B)
                printf("%d\n", s[x][y]);
            else {
                int ans = 0;
                
                for (int i = y; i <= n; i += x)
                    ans += a[i];
                
                printf("%d\n", ans);
            }
        } else {
            for (int i = 1; i < B; ++i)
                s[i][x % i] += y - a[x];
            
            a[x] = y;
        }
    }

    return 0;
}

P8250 交友问题

给定一个 \(n\) 个点 \(m\) 条边的无向图,\(q\) 次询问,每次给出两个点 \(u, v\) ,求有多少与 \(u\) 相邻的点不与 \(v\) 相邻且不为 \(v\)

\(n, q \le 2 \times 10^5\)\(m \le 7 \times 10^5\)

设阈值为 \(B\) ,将所有度数 \(> B\) 的点称为重点,剩下点称为轻点,显然重点的个数不超过 \(\frac{2m - 2}{B}\)

bitset 存储重点的出边,用vectorset 存储轻点的出边。查询时分类讨论:

  • 重点对重点:用 bitset 记录每个点的连边,查询时异或即可,时间复杂度 \(O(\frac{n}{w})\)
  • 重点对轻点:先设答案为重点的度数,枚举轻点的所有出点判断即可,时间复杂度 \(O(B)\)
  • 轻点对重点:枚举轻点的所有出点。判断是否是重点的出点即可,时间复杂度 \(O(B)\)
  • 轻点对轻点:枚举 \(u\) 的所有出点判断是否为 \(v\) 的出点即可,时间复杂度 \(O(B \log n)\)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7, S = 711;

struct Graph {
    vector<int> e[N];
    
    inline void insert(int u, int v) {
        e[u].emplace_back(v);
    }
} G;

bitset<N> e1[S];
set<int> e2[N];

int id[N];

int n, m, q, tot;

signed main() {
    scanf("%d%d%d", &n, &m, &q);

    for (int i = 1; i <= m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        G.insert(u, v), G.insert(v, u);
    }

    int B = sqrt(m * 2);

    for (int i = 1; i <= n; ++i) {
        if (G.e[i].size() > B) {
            id[i] = ++tot;

            for (int it : G.e[i])
                e1[id[i]].set(it);
        } else {
            for (int it : G.e[i])
                e2[i].emplace(it);
        }
    }

    while (q--) {
        int u, v;
        scanf("%d%d", &u, &v);

        if (id[u] && id[v]) {
            bitset<N> ans = e1[id[u]] ^ (e1[id[u]] & e1[id[v]]);
            ans.reset(u), ans.reset(v);
            printf("%d\n", ans.count());
        } else if (id[u]) {
            int ans = G.e[u].size();

            for (int it : G.e[v])
                if (it == u || it == v || e1[id[u]].test(it))
                    --ans;

            printf("%d\n", ans);
        } else if (id[v]) {
            int ans = 0;

            for (int it : G.e[u])
                if (it != u && it != v && !e1[id[v]].test(it))
                    ++ans;

            printf("%d\n", ans);
        } else {
            int ans = 0;

            for (int it : G.e[u])
                if (it != u && it != v && e2[v].find(it) == e2[v].end())
                    ++ans;

            printf("%d\n", ans);
        }
    }

    return 0;
}

[ARC052D] 9

给定 \(k, m\) ,求有多少个 \(n\) 满足 \(1 \le n \le m\)\(n \equiv S(n) \pmod{k}\) ,其中 \(S(n)\) 表示 \(n\) 十进制下各位数字之和。

\(1 \le k, m \le 10^{10}\)

记阈值为 \(B\)

  • \(k \ge B\) 时,因为最大的 \(S(n) \le 9 \times 10 = 90\) ,于是可以枚举数字和 \(S\) ,由于 \(n \bmod{k} = S\)\(n\) 的个数不会超过 \(\lfloor \frac{m}{k} \rfloor + 1\) 个,直接暴力即可,时间复杂度 \(O(90 \times \frac{m}{k})\)
  • \(k < B\) 时,设 \(f_{i, j, s, 0/1}\) 表示考虑到从高到低的第 \(i\) 位,此时数字模 \(k\)\(j\) ,数字和为 \(s\) ,是否已经小于 \(m\) 的数的个数,直接 DP 可以做到 \(O(10 \times 90 \times k \times 10) = O(9000k)\)

\(B = 10^4\) 即可。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

ll m, k;

namespace Method1 {
inline ll calc(ll x) {
    ll res = 0;
    
    while (x)
        res += x % 10, x /= 10;
    
    return res;
}

inline void solve() {
    ll ans = 0;
    
    for (ll s = 0; s <= 90; ++s)
        for (ll i = s; i <= m; i += k)
            if (calc(i) % k == s)
                ++ans;
    
    printf("%lld", ans - 1);
}
} // namespace Method1

namespace Method2 {
ll f[11][10001][91][2];

inline vector<ll> getcount(ll x) {
    vector<ll> res;
    
    while (x)
        res.push_back(x % 10), x /= 10;
    
    return reverse(res.begin(), res.end()), res;
}

inline void solve() {
    vector<ll> vec = getcount(m);
    f[0][0][0][0] = 1;
    
    for (ll i = 0; i < vec.size(); ++i)
        for (ll j = 0; j < k; ++j)
            for (ll s = 0; s <= 90; ++s) {
                for (ll t = 0; t < vec[i]; ++t)
                    if (s + t <= 90)
                        f[i + 1][(j * 10 + t) % k][s + t][1] += f[i][j][s][0];
                
                if (s + vec[i] <= 90)
                    f[i + 1][(j * 10 + vec[i]) % k][s + vec[i]][0] += f[i][j][s][0];
                
                for (ll t = 0; t < 10; ++t)
                    if (s + t <= 90)
                        f[i + 1][(j * 10 + t) % k][s + t][1] += f[i][j][s][1];
            }
    
    ll ans = 0;
    
    for (ll i = 0; i <= 90; ++i)
        ans += f[vec.size()][i % k][i][0] + f[vec.size()][i % k][i][1];
    
    printf("%lld", ans - 1);
}
} // namespace Method2

signed main() {
    scanf("%lld%lld", &k, &m);
    
    if (k >= 1e4)
        Method1::solve();
    else
        Method2::solve();
    
    return 0;
}

[ABC259Ex]Yet Another Path Counting

一个 \(n \times n\) 的网格图,每个格子上有颜色,求满足起点和终点颜色均相同且只向下和向右走的路径条数。

\(n \le 400\)

算法一:设起点为 \((x, y)\) ,终点为 \((z, w)\) ,则路径条数为 \(\binom{x - z + y - w}{x - z}\) 。对每种颜色枚举起点和终点,容易计算答案。

算法二:对于每种颜色 \(c\) 都做一遍,设 \(f_{i, j}\) 表示以 \((i, j)\) 结尾的合法路径条数,于是 \(f_{i, j} = f_{i, j} + f_{i, j - 1} + f_{i - 1, j}\) 。所有颜色为 \(c\) 位置 \((i, j)\) 初值为 \(1\) ,否则为 \(0\)

对于这种分颜色处理的问题,很经典的解决方式是对颜色出现次数进行根号分治

记阈值为 \(B\) ,则最多有 \(\frac{n^2}{B}\) 中颜色出现超过 \(B\) 次,对这部分 DP 剩余部分用组合数计算。

时间复杂度 \(O(nB^2 + \frac{n^4}{B})\) ,当 \(B = n\) 时取得最小值 \(O(n^3)\)

#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353;
const int N = 8e2 + 7;

vector<pair<int, int> > p[N * N];

int a[N][N], f[N][N], fac[N], inv[N], invfac[N];

int n, ans;

inline int add(int x, int y) {
    x += y;
    
    if (x >= Mod)
        x -= Mod;
    
    return x;
}

inline int dec(int x, int y) {
    x -= y;
    
    if (x < 0)
        x += Mod;
    
    return x;
}

inline void prework(int n) {
    fac[0] = fac[1] = 1;
    inv[0] = inv[1] = 1;
    invfac[0] = invfac[1] = 1;
    
    for (int i = 2; i <= n; ++i) {
        fac[i] = 1ll * fac[i - 1] * i % Mod;
        inv[i] = 1ll * (Mod - Mod / i) * inv[Mod % i] % Mod;
        invfac[i] = 1ll * invfac[i - 1] * inv[i] % Mod;
    }
}

inline int C(int n, int m) {
    return m > n ? 0 : 1ll * fac[n] * invfac[m] % Mod * invfac[n - m] % Mod;
}

signed main() {
    scanf("%d", &n), prework(n * 2);
    
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            scanf("%d", a[i] + j), p[a[i][j]].emplace_back(i, j);

    int ans = 0;
    
    for (int c = 1; c <= n * n; ++c) {
        if (p[c].size() <= n) {
            for (auto x : p[c])
                for (auto y : p[c])
                    if (x.first <= y.first && x.second <= y.second)
                        ans = add(ans, C(y.first - x.first + y.second - x.second, y.first - x.first));
        } else {
            for (int i = 1; i <= n; ++i)
                for (int j = 1; j <= n; ++j) {
                    if (a[i][j] == c)
                        ans = add(ans, f[i][j] = add(f[i - 1][j], add(f[i][j - 1], 1)));
                    else
                        f[i][j] = add(f[i - 1][j], f[i][j - 1]);
                }
        }
    }
    
    printf("%d", ans);
    return 0;
}

CF1039D You Are Given a Tree

给出一棵树,对每个 \(k \in [1, n]\) ,求出最多能找出多少条不交的 \(k\) 个点的链。

\(n \le 10^5\)

设阈值为 \(B\)

对于 \(k \le B\) ,直接暴力做树上 DP,时间复杂度 \(O(nB)\)

对于 \(k \in [B + 1, n]\) ,则答案 \(\le \frac{n}{B}\) ,且答案单调不升。考虑枚举答案,二分找到这个答案对应的 \(k\) 的范围,时间复杂度 \(O(\frac{n}{B} \times n \log n)\)

\(B = \sqrt{n \log n}\) ,时间复杂度 \(O(n \sqrt{n \log n})\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;

struct Graph {
    vector<int> e[N];
    
    inline void insert(int u, int v) {
        e[u].emplace_back(v);
    }
} G;

int fa[N], id[N], f[N], ans[N];

int n, dfstime;

void dfs(int u, int f) {
    fa[u] = f, id[++dfstime] = u;

    for (int v : G.e[u])
        if (v != f)
            dfs(v, u);
}

inline int solve(int k) {
    fill(f + 1, f + n + 1, 1);
    int res = 0;

    for (int i = n; i; --i) {
        int u = id[i];

        if (fa[u] && ~f[u] && ~f[fa[u]]) {
            if (f[u] + f[fa[u]] >= k)
                ++res, f[fa[u]] = -1;
            else
                f[fa[u]] = max(f[fa[u]], f[u] + 1);
        }
    }

    return res;
}

signed main() {
    scanf("%d", &n);

    for (int i = 1; i < n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        G.insert(u, v), G.insert(v, u);
    }

    dfs(1, 0), ans[1] = n;
    int B = sqrt(n * __lg(n) + 1);

    for (int i = 2; i <= B; ++i)
        ans[i] = solve(i);

    for (int i = B + 1; i <= n; ++i) {
        int res = solve(i), l = i, r = n, mxr = i;

        while (l <= r) {
            int mid = (l + r) >> 1;

            if (solve(mid) == res)
                mxr = mid, l = mid + 1;
            else
                r = mid - 1;
        }

        fill(ans + i, ans + mxr + 1, res), i = mxr;
    }

    for (int i = 1; i <= n; ++i)
        printf("%d\n", ans[i]);

    return 0;
}

P9994 [Ynoi Easy Round 2024] TEST_132

给出平面上的 \(n\) 个点,第 \(i\) 个点坐标为 \((x_i, y_i)\) ,初始权值为 \(v_i\)

\(m\) 次操作,操作有:

  • 给出 \(X\) ,修改 \(x_i = X\) 的点的点权为 \(w_i^2\)
  • 给出 \(Y\) ,查询 \(y_i = Y\) 的点的点权和 \(\bmod (10^9 + 7)\)

\(n, m \le 1.2 \times 10^6\) ,TL = 12s

考虑离线后对行的点数根号平衡处理。

对于点数 \(\le B\) 的行,修改可以暴力处理,这部分复杂度是 \(O(mB)\) 的。

对于点数 \(> B\) 的行,考虑询问时单独统计这些行,这部分复杂度是 \(O(\frac{nm}{B} \log V)\) 的,后面的 \(\log V\) 是快速幂的复杂度。

事实上快速幂的部分是可以优化成光速幂的,由于最终要求的形式为 \(v_i^{2^t \bmod (p - 1)}\) ,因此可以对每个 \(v_i\) 预处理其 \(2^0, 2^8, 2^{16}, 2^{24}\) 次幂的 \(0 \sim 2^8\) 次幂,即可做到 \(O(1)\) 查询。

\(B = \sqrt{n}\) ,时间复杂度 \(O(m \sqrt{n})\) ,块长调成 \(2000\) 跑的快些。

#include <bits/stdc++.h>
using namespace std;
const int Mod = 1e9 + 7;
const int N = 1.2e6 + 7;

struct Node {
    int op, x;
} nd[N];

vector<pair<int, int> > vec[N];
vector<int> qry[N];

int pw[N], sum[N], ans[N], pre[N];

int n, m;

template <class T = int>
inline T read() {
    char c = getchar();
    bool sign = (c == '-');
    
    while (c < '0' || c > '9')
        c = getchar(), sign |= (c == '-');
    
    T x = 0;
    
    while ('0' <= c && c <= '9')
        x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    
    return sign ? (~x + 1) : x;
}

inline int add(int x, int y) {
    x += y;
    
    if (x >= Mod)
        x -= Mod;
    
    return x;
}

inline int dec(int x, int y) {
    x -= y;
    
    if (x < 0)
        x += Mod;
    
    return x;
}

namespace Pow {
const int B = 1 << 8;

int pw[4][B];

inline void prework(int k) {
    for (int i = 0; i < 4; k = 1ll * k * pw[i++][B - 1] % Mod) {
        pw[i][0] = 1;

        for (int j = 1; j < B; ++j)
            pw[i][j] = 1ll * pw[i][j - 1] * k % Mod;
    }
}

inline int query(int k) {
    return 1ll * pw[3][k >> 24] * pw[2][k >> 16 & 255] % Mod * pw[1][k >> 8 & 255] % Mod * pw[0][k & 255] % Mod;
}
} // namespace Pow

signed main() {
    n = read(), m = read();

    for (int i = 1; i <= n; ++i) {
        int x = read(), y = read(), k = read();
        vec[x].emplace_back(y, k);
    }

    int B = 2e3;

    for (int i = 1; i <= n; ++i)
        if (vec[i].size() <= B) {
            for (auto it : vec[i])
                sum[it.first] = add(sum[it.first], it.second);
        }

    for (int i = 1; i <= m; ++i) {
        nd[i].op = read(), nd[i].x = read();

        if (nd[i].op == 1) {
            if (vec[nd[i].x].size() <= B) {
                for (auto &it : vec[nd[i].x]) {
                    sum[it.first] = dec(sum[it.first], it.second);
                    sum[it.first] = add(sum[it.first], it.second = 1ll * it.second * it.second % Mod);
                }
            }
        } else
            ans[i] = sum[nd[i].x], qry[nd[i].x].emplace_back(i);
    }

    pw[0] = 1;

    for (int i = 1; i <= m; ++i)
        pw[i] = 2ll * pw[i - 1] % (Mod - 1);

    for (int i = 1; i <= n; ++i) {
        if (vec[i].size() <= B)
            continue;

        for (int j = 1; j <= m; ++j)
            pre[j] = pre[j - 1] + (nd[j].op == 1 && nd[j].x == i);

        for (auto it : vec[i]) {
            Pow::prework(it.second);

            for (int x : qry[it.first])
                ans[x] = add(ans[x], Pow::query(pw[pre[x]]));
        }
    }

    for (int i = 1; i <= m; ++i)
        if (nd[i].op == 2)
            printf("%d\n", ans[i]);

    return 0;
}

CF1793F Rebrending

给定 \(a_{1 \sim n}\)\(q\) 次询问,每次给出区间 \([l, r]\) ,求 \(\min_{l \le i < j \le r} |a_i - a_j|\)

\(a_i \le n \le 3 \times 10^5\)\(q \le 10^6\)

考虑将询问按右端点离线扫描线。

对于 \(r - l \le \sqrt{n}\) 的询问,考虑动态维护 \(f_i\) 表示 \([i, r]\) 的答案,显然只要维护 \(r - i \le \sqrt{n}\) 的部分即可,每次暴力更新即可做到 \(O(n \sqrt{n} + q)\)

对于 \(r - l > \sqrt{n}\) 的询问,显然答案 \(\le \sqrt{n}\) 。考虑动态维护 \(g_i\) 表示 \([g_i, r]\) 答案 \(\le i\) 的下标最大的位置,显然只要拿出 \([a_i - \sqrt{n}, a_i + \sqrt{n}]\) 的数的最后一次出现的位置更新即可,查询时暴力双指针即可做到 \(O(n \sqrt{n} + q)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 7, Q = 1e6 + 7;

vector<pair<int, int> > tmp[N], qry1[N], qry2[N];

int a[N], f[N], g[N], lst[N], ans[Q];

int n, q;

signed main() {
    scanf("%d%d", &n, &q);

    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);

    int B = sqrt(n);

    for (int i = 1; i <= q; ++i) {
        int l, r;
        scanf("%d%d", &l, &r);

        if (r - l <= B)
            qry1[r].emplace_back(l, i);
        else
            tmp[r - l].emplace_back(l, i);
    }

    for (int i = 1; i <= n; ++i)
        for (auto it : tmp[i])
            qry2[it.first + i].emplace_back(it);

    fill(f + 1, f + n + 1, n);

    for (int i = 1; i <= n; lst[a[i]] = i, ++i) {
        for (int j = i - 1; j >= i - B; --j)
            f[j] = min(f[j], min(f[j + 1], abs(a[i] - a[j])));

        for (auto it : qry1[i])
            ans[it.second] = f[it.first];

        for (int j = max(1, a[i] - B); j <= min(n, a[i] + B); ++j)
            g[abs(a[i] - j)] = max(g[abs(a[i] - j)], lst[j]);

        for (int i = 1; i <= B; ++i)
            g[i] = max(g[i], g[i - 1]);

        int j = B;

        for (auto it : qry2[i]) {
            while (j && g[j - 1] >= it.first)
                --j;

            ans[it.second] = j;
        }
    }

    for (int i = 1; i <= q; ++i)
        printf("%d\n", ans[i]);
    
    return 0;
}

块状数组

主要是支持区间加-区间求和,一般有两种:

  • \(O(\sqrt{n}) - O(1)\)
  • \(O(1) - O(\sqrt{n})\)

当修改和查询不同阶时可以用于平衡复杂度。

P6782 [Ynoi2008] rplexq

给定一棵树,\(m\) 次询问,每次给出 \(l, r, x\) ,求有多少二元组 \((i, j)\) 满足 \(l \le i < j \le r\)\(\mathrm{LCA}(i, j) = x\)

\(n, m \le 2 \times 10^5\) ,ML = 128MB

先将问题转化为求 \(\frac{1}{2}(f^2(x, l, r) - [x \in [l, r]] - \sum_{y \in son(x)} f^2(y, l, r))\) ,其中 \(f(x, l, r)\) 表示 \(x\) 子树内 \(\in [l, r]\) 的点数。

考虑将每个点 \(x\) 的儿子中 \(siz\)\(\min(|son(x)|, \sqrt{n})\) 大的儿子拿出来,直接暴力将 \([l, r]\) 挂在这些点上,则查询相当于二维数点。由于修改数量为 \(O(n)\) 、询问数量为 \(O(m \sqrt{n})\) ,因此考虑用 \(O(\sqrt{n}) - O(1)\) 的分块维护,该部分复杂度为 \(O((n + m) \sqrt{n})\)

但是直接存储询问空间会炸,考虑优化删去儿子这部分的查询。发现每次查询的 dfn 区间连续且互不相交,因此查询完一个儿子后再将其挂到后面的儿子上,及时清空即可做到空间复杂度线性。

对于剩下的儿子,考虑把所有子树拿出来暴力跑莫队,不难发现拿出的总点数为 \(O(n)\) ,因此该部分复杂度为 \(O(n \sqrt{m})\)

由于第一部分跑得很满,而莫队跑得不满,因此可以适当将阈值减小以减小常数。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7, B = 57;

struct Graph {
    vector<int> e[N];
    
    inline void insert(int u, int v) {
        e[u].emplace_back(v);
    }
} G;

struct Query {
    ll ans;
    int l, r, x, lst1, lst2, lim;
} qry[N];

ll ans[N];
int fa[N], siz[N], in[N], out[N], id[N];

int n, m, rt, dfstime;

void dfs1(int u, int f) {
    fa[u] = f, siz[u] = 1;

    for (int v : G.e[u])
        if (v != f)
            dfs1(v, u), siz[u] += siz[v];
}

void dfs2(int u) {
    id[in[u] = ++dfstime] = u;

    for (int v : G.e[u])
        dfs2(v);

    out[u] = dfstime;
}

namespace Method1 {
vector<int> qin[2][N], qout[2][N];

inline void insert(int id) {
    int u = qry[id].x;
    qin[0][in[u]].emplace_back(id);
    qry[id].lim = out[G.e[u].size() <= B ? u : G.e[u][B - 1]];
}

namespace FK {
int s[N], tag[N];

int block;

inline void update(int x, int k) {
    int bel = (x + block - 1) / block;

    for (int i = x; i <= min(n, bel * block); ++i)
        s[i] += k;

    for (int i = bel + 1; i <= (n + block - 1) / block; ++i)
        tag[i] += k;
}

inline int ask(int x) {
    return s[x] + tag[(x + block - 1) / block];
}

inline int query(int l, int r) {
    return ask(r) - ask(l - 1);
}
} // namespace FK

inline void solve() {
    FK::block = sqrt(n);

    for (int i = 1; i <= n; ++i) {
        for (int it : qin[0][i]) {
            qry[it].lst1 = FK::query(qry[it].l, qry[it].r);
            qin[1][out[id[i]]].emplace_back(it);

            if (i + 1 <= qry[it].lim)
                qout[0][i + 1].emplace_back(it);
        }

        for (int it : qout[0][i]) {
            qry[it].lst2 = FK::query(qry[it].l, qry[it].r);
            qout[1][out[id[i]]].emplace_back(it);
        }

        qin[0][i].clear(), qin[0][i].shrink_to_fit();
        qout[0][i].clear(), qout[0][i].shrink_to_fit();
        FK::update(id[i], 1);

        for (int it : qin[1][i]) {
            int res = FK::query(qry[it].l, qry[it].r) - qry[it].lst1;
            qry[it].ans += 1ll * res * res;
        }

        for (int it : qout[1][i]) {
            int res = FK::query(qry[it].l, qry[it].r) - qry[it].lst2;
            qry[it].ans -= 1ll * res * res;

            if (i + 1 <= qry[it].lim)
                qout[0][i + 1].emplace_back(it);
        }

        qin[1][i].clear(), qin[1][i].shrink_to_fit();
        qout[1][i].clear(), qout[1][i].shrink_to_fit();
    }
}
} // namespace Method1

namespace Method2 {
vector<int> q[N];

int seq[N], bel[N], cnt[N];

int tot;

inline void insert(int id) {
    if (G.e[qry[id].x].size() > B)
        q[qry[id].x].emplace_back(id);
}

void dfs(int u, int r) {
    seq[++tot] = u, bel[u] = r;

    for (int v : G.e[u])
        dfs(v, r);
}

inline void solve() {
    for (int x = 1; x <= n; ++x) {
        if (G.e[x].size() <= B || q[x].empty())
            continue;

        seq[tot = 1] = 0;

        for (int i = B; i < G.e[x].size(); ++i)
            dfs(G.e[x][i], G.e[x][i]), cnt[G.e[x][i]] = 0;

        seq[++tot] = n + 1;
        sort(seq + 1, seq + tot + 1);
        int block = sqrt(tot);

        sort(q[x].begin(), q[x].end(), [&](const int &a, const int &b) {
            int ida = qry[a].l / block, idb = qry[b].l / block;
            return ida == idb ? (ida & 1 ? qry[a].r > qry[b].r : qry[a].r < qry[b].r) : ida < idb;
        });

        ll result = 0;
        int l = 1, r = 0;

        auto update = [&](int x, int k) {
            if (!x || x == n + 1)
                return;

            result -= 1ll * cnt[bel[x]] * cnt[bel[x]];
            cnt[bel[x]] += k;
            result += 1ll * cnt[bel[x]] * cnt[bel[x]];
        };

        for (auto it : q[x]) {
            while (seq[l] > qry[it].l)
                update(seq[--l], 1);

            while (seq[r] < qry[it].r)
                update(seq[++r], 1);

            while (seq[l] < qry[it].l)
                update(seq[l++], -1);

            while (seq[r] > qry[it].r)
                update(seq[r--], -1);

            qry[it].ans -= result;
        }
    }
}
} // namespace Method2

signed main() {
    scanf("%d%d%d", &n, &m, &rt);

    for (int i = 1; i < n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        G.insert(u, v), G.insert(v, u);
    }

    dfs1(rt, 0);

    for (int i = 1; i <= n; ++i)
        if (i != rt) {
            G.e[i].erase(find(G.e[i].begin(), G.e[i].end(), fa[i]));

            sort(G.e[i].begin(), G.e[i].end(), [](const int &a, const int &b) {
                return siz[a] > siz[b];
            });
        }

    dfs2(rt);

    for (int i = 1; i <= m; ++i) {
        scanf("%d%d%d", &qry[i].l, &qry[i].r, &qry[i].x);
        Method1::insert(i), Method2::insert(i);
    }

    Method1::solve(), Method2::solve();

    for (int i = 1; i <= m; ++i)
        printf("%lld\n", (qry[i].ans - (qry[i].l <= qry[i].x && qry[i].x <= qry[i].r)) / 2);

    return 0;
}

值域倍增分块

P7447 [Ynoi2007] rgxsxrs

给定序列 \(a_{1 \sim n}\)\(m\) 次操作,操作有:

  • 将区间内 \(> x\) 的数减去 \(x\)
  • 查询区间和、最小值、最大值。

\(n, m \le 5 \times 10^5\)\(1 \le a_i, x \le 10^9\) ,强制在线,ML = 64 MB

考虑值域倍增分块,进制为 \(B\) ,则将值域 \(\in [B^i, B^{i + 1})\) 分在一个块内,对每一块建立线段树。

修改时再每一棵线段树上暴力,维护势能线段树:

  • 若区间最大值 \(\le x\) ,直接退出。
  • 若区间最小值减去 \(B^i\) 的值 \(\ge x\) ,直接打标记。
  • 否则递归到叶子节点,就把这个叶子删除并丢到次数更低的块。

查询在每一块上面分别查询后合并即可。由于每个叶子只会下放 \(O(\log_B V)\) 次,每次插入都要 \(O(\log n)\) 的复杂度,因此时间复杂度为 \(O((n + m) \log_B V \log_2 n + m B \log_2 n \log_B V)\) ,空间复杂度 \(O(n \log_B V)\)

考虑优化空间,发现瓶颈在线段树,考虑线段树底层分块,取块长为 \(\log_B V\) 即可。

理论上取 \(B = e\) 最优,但是由于常数问题取 \(B = 32\) 表现比较优秀。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 5e5 + 7, B = 32, L = 6;

int a[N], bel[N], pw[L];

int n, m;

inline int getid(int x) {
    return __lg(x) / __lg(B);
}

namespace SMT {
const int S = N << 4, M = 16;

ll s[S];
int rt[L], lc[S], rc[S], mn[S], mx[S], tag[S], cnt[S];

int tot;

inline void pushup(int x) {
    s[x] = s[lc[x]] + s[rc[x]], cnt[x] = cnt[lc[x]] + cnt[rc[x]];
    mn[x] = min(mn[lc[x]], mn[rc[x]]), mx[x] = max(mx[lc[x]], mx[rc[x]]);
}

inline void rebuild(int id, int x, int l, int r) {
    mn[x] = inf, mx[x] = -inf, s[x] = cnt[x] = 0;

    for (int i = l; i <= r; ++i) {
        if (bel[i] != id)
            continue;

        mn[x] = min(mn[x], a[i]), mx[x] = max(mx[x], a[i]);
        s[x] += a[i], ++cnt[x];
    }
}

inline void spread(int x, int k) {
    if (cnt[x])
        s[x] += 1ll * cnt[x] * k, mn[x] += k, mx[x] += k, tag[x] += k;
}

inline void pushdown(int id, int x, int l, int r) {
    if (r - l + 1 <= M) {
        for (int i = l; i <= r; ++i)
            if (bel[i] == id)
                a[i] += tag[x];
    } else
        spread(lc[x], tag[x]), spread(rc[x], tag[x]);

    tag[x] = 0;
}

void build(int id, int &x, int l, int r) {
    x = ++tot;

    if (r - l + 1 <= M) {
        rebuild(id, x, l, r);
        return;
    }

    int mid = (l + r) >> 1;
    build(id, lc[x], l, mid), build(id, rc[x], mid + 1, r);
    pushup(x);
}

void insert(int id, int x, int nl, int nr, int p, int k) {
    if (tag[x])
        pushdown(id, x, nl, nr);

    if (nr - nl + 1 <= M) {
        bel[p] = id, s[x] += k, ++cnt[x], mn[x] = min(mn[x], k), mx[x] = max(mx[x], k);
        return;
    }

    int mid = (nl + nr) >> 1;

    if (p <= mid)
        insert(id, lc[x], nl, mid, p, k);
    else
        insert(id, rc[x], mid + 1, nr, p, k);

    pushup(x);
}

void update(int id, int x, int nl, int nr, int l, int r, int k) {
    if (!cnt[x] || mx[x] <= k)
        return;
    else if (l <= nl && nr <= r && mn[x] - k >= pw[id]) {
        spread(x, -k);
        return;
    }

    if (tag[x])
        pushdown(id, x, nl, nr);

    if (nr - nl + 1 <= M) {
        l = max(nl, l), r = min(nr, r);

        for (int i = l; i <= r; ++i)
            if (bel[i] == id && a[i] > k) {
                a[i] -= k;

                if (a[i] < pw[id])
                    insert(getid(a[i]), rt[getid(a[i])], 1, n, i, a[i]);
            }

        rebuild(id, x, nl, nr);
        return;
    }

    int mid = (nl + nr) >> 1;

    if (l <= mid)
        update(id, lc[x], nl, mid, l, r, k);

    if (r > mid)
        update(id, rc[x], mid + 1, nr, l, r, k);

    pushup(x);
}

ll querysum(int id, int x, int nl, int nr, int l, int r) {
    if (!cnt[x])
        return 0;
    else if (l <= nl && nr <= r)
        return s[x];

    if (tag[x])
        pushdown(id, x, nl, nr);

    if (nr - nl + 1 <= M) {
        l = max(nl, l), r = min(nr, r);
        ll res = 0;

        for (int i = l; i <= r; ++i)
            if (bel[i] == id)
                res += a[i];

        return res;
    }

    int mid = (nl + nr) >> 1;

    if (r <= mid)
        return querysum(id, lc[x], nl, mid, l, r);
    else if (l > mid)
        return querysum(id, rc[x], mid + 1, nr, l, r);
    else
        return querysum(id, lc[x], nl, mid, l, r) + querysum(id, rc[x], mid + 1, nr, l, r);
}

int querymin(int id, int x, int nl, int nr, int l, int r) {
    if (!cnt[x])
        return inf;
    else if (l <= nl && nr <= r)
        return mn[x];

    if (tag[x])
        pushdown(id, x, nl, nr);

    if (nr - nl + 1 <= M) {
        l = max(nl, l), r = min(nr, r);
        int res = inf;

        for (int i = l; i <= r; ++i)
            if (bel[i] == id)
                res = min(res, a[i]);

        return res;
    }

    int mid = (nl + nr) >> 1;

    if (r <= mid)
        return querymin(id, lc[x], nl, mid, l, r);
    else if (l > mid)
        return querymin(id, rc[x], mid + 1, nr, l, r);
    else
        return min(querymin(id, lc[x], nl, mid, l, r), querymin(id, rc[x], mid + 1, nr, l, r));
}

int querymax(int id, int x, int nl, int nr, int l, int r) {
    if (!cnt[x])
        return -inf;
    else if (l <= nl && nr <= r)
        return mx[x];

    if (tag[x])
        pushdown(id, x, nl, nr);

    if (nr - nl + 1 <= M) {
        l = max(nl, l), r = min(nr, r);
        int res = -inf;

        for (int i = l; i <= r; ++i)
            if (bel[i] == id)
                res = max(res, a[i]);

        return res;
    }

    int mid = (nl + nr) >> 1;

    if (r <= mid)
        return querymax(id, lc[x], nl, mid, l, r);
    else if (l > mid)
        return querymax(id, rc[x], mid + 1, nr, l, r);
    else
        return max(querymax(id, lc[x], nl, mid, l, r), querymax(id, rc[x], mid + 1, nr, l, r));
}
} // namespace SMT

inline void update(int l, int r, int k) {
    for (int i = getid(k); i < L; ++i)
        SMT::update(i, SMT::rt[i], 1, n, l, r, k);
}

inline ll querysum(int l, int r) {
    ll res = 0;

    for (int i = 0; i < L; ++i)
        res += SMT::querysum(i, SMT::rt[i], 1, n, l, r);

    return res;
}

inline int querymin(int l, int r) {
    for (int i = 0; i < L; ++i) {
        int res = SMT::querymin(i, SMT::rt[i], 1, n, l, r);

        if (res != inf)
            return res;
    }
}

inline int querymax(int l, int r) {
    for (int i = L - 1; ~i; --i) {
        int res = SMT::querymax(i, SMT::rt[i], 1, n, l, r);

        if (res != -inf)
            return res;
    }
}

signed main() {
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i), bel[i] = getid(a[i]);

    pw[0] = 1;

    for (int i = 1; i < L; ++i)
        pw[i] = pw[i - 1] * B;

    for (int i = 0; i < L; ++i)
        SMT::build(i, SMT::rt[i], 1, n);

    int lstans = 0;

    while (m--) {
        int op, l, r;
        scanf("%d%d%d", &op, &l, &r);
        l ^= lstans, r ^= lstans;

        if (op == 1) {
            int k;
            scanf("%d", &k);
            update(l, r, k ^ lstans);
        } else {
            ll res = querysum(l, r);
            printf("%lld %d %d\n", res, querymin(l, r), querymax(l, r));
            lstans = res & ((1 << 20) - 1);
        }
    }

    return 0;
}
posted @ 2025-06-25 16:15  wshcl  阅读(29)  评论(0)    收藏  举报