NOI 2025 见!

 

题解:AT_abc438_g [ABC438G] Sum of Min

题目链接

比较直接的刻画是:把 \(A\)\(B\) 分别首尾相连,形成两个环;有两枚棋子 \(a, b\),初始时分别在 \(A, B\) 的下标 \(0\) 上,每个时刻移动一步,一共移动 \(K\) 步。

经典结论是,在恰好 \(\operatorname{lcm}(N, M)\) 步后,\((a, b)\) 才会首次回到 \((0, 0)\)。这对 \(N, M \leq 200000\) 来说太过奢侈,需要换一种刻画方式。


两枚棋子一起动还是太难观察了。考虑固定 \(a\)\(x\) 时,找出 \(b\) 可能在的所有 \(y\)。设 \(S_x\)\(a\)\(x\) 时的所有时刻组成的集合,形式化的: \(\{ i \in \mathbb{Z} \mid 0\le i<K,\ i \bmod N = x \}\);用 \(i\) 来从小到大遍历 \(S_x\) 的所有元素,则 \(b\) 会依次落在 \(i \bmod M\) 上。

因此题目所求,有以下的等价变换:

\[\sum_{i=0}^{K-1} \min (A_{i \bmod N},B_{i \bmod M}) = \sum_{x=0}^{N-1} \sum_{i \in S_x} \min (A_x, B_{i \bmod M}) \]

从求和式本身考虑,后者只是利用了加法的交换律与结合律,按左式的 \(i \bmod N\) 对求和项进行了分组,因此自然成立。接下来只需考虑:对于一个固定的 \(x\),如何快速计算 \(\sum_{i \in S_x} \min (A_x, B_{i \bmod M})\)


\(x \ge K\),则 \(S_x=\varnothing\),对答案无任何贡献;因此下文只讨论 \(x<K\) 的情形。

在用 \(i\) 以从小到大的顺序遍历 \(S_x\) 的所有元素时,\(i \bmod M\) 的轨迹可以这样刻画:有一枚棋子 \(q\) 初始在 \(x \bmod M\),设每次移动前,它所在的下标为 \(p\);那么移动后的下标会是 \((p + N) \bmod M\),一共会移动 \(|S_x|\),即 \(\lceil \frac{K - x}{N} \rceil\) 步。

实际上,\(q\) 移动 \(\frac{M}{\gcd(N, M)}\) 步会首次回到 \(x \bmod M\),也就是说,\(q\) 总沿着一个环长为 \(\frac{M}{\gcd(N, M)}\) 的简单环移动。


先来证明为什么是简单环。根本原因是,

\[f:\{0,1,\dots,M-1\}\to\{0,1,\dots,M-1\},\quad f(i)=(i+N)\bmod M \]

是一个双射,即在 \(\{0,1,\dots,M-1\}\) 上的一个置换,因此 \(q\) 必定在 \(x \bmod M\) 所在的一个置换环上移动。

再来证明环长为 \(\frac{M}{\gcd(N, M)}\)。按照定义,\(q\) 在第 \(t\) 步会抵达 \((x + tN) \bmod M\)
因此求环长相当于求一个最小的正整数 \(t\),满足:

\[x+tN \equiv x \pmod M \iff tN \equiv 0 \pmod M \iff M \mid tN. \]

因此最小的满足条件的正整数 \(t\),即环长为 \(\frac{M}{\gcd(N,M)}\)

证明过程中体现——无论 \(x\) 取何值,\(t\) 总是 \(\frac{M}{\gcd(N,M)}\)。因此 \(f\) 的每个置换环的环长都会是 \(\frac{M}{\gcd(N, M)}\);又因为 \(f\) 是在 \(\{0,1,\dots,M-1\}\) 上的一个置换,因此共有 \(\frac{M}{\frac{M}{\gcd(N, M)}} = \gcd(N, M)\) 个置换环。

以下记环长,即 \(\frac{M}{\gcd(N,M)}\)\(\text{len}\)


下文记 \(\text{step}\)\(|S_x| + 1\),即 \(\lceil \frac{K - x}{N} \rceil + 1\)

有了以上结论的成立,就有了最终刻画:对于一个固定的 \(x\)\(q\) 会走过 \(\left\lfloor \frac{\text{step}}{\text{len}} \right\rfloor\) 次整个置换环,然后从 \(x \bmod M\) 出发再走 \(\text{step} \bmod \text{len}\) 步(这里的步数包括自身,即走 \(0\) 步定义为无动作,走 \(1\) 步会到达 \(x \bmod M\))。

\(y\) 遍历了 \(q\) 走过的所有下标,那么 \(x\) 的贡献是所有的 \(\min(A_x, B_y)\) 的和。

走一次完整的置换环的贡献是固定的,因此每次只需统计整环与余下的步数的贡献即可,需要走 \(O(\text{len})\) 数量级步。可惜的是,总时间复杂度仍是 \(O(NM)\) 级别的,仍需继续优化。


为了便于加速统计,先将置换环破环为链。形式化地,对 \(x\) 所在的置换环任选一条边并将其断开。若断开后不存在仍保留出边的端点(此时置换环为自环),则无需进一步处理;否则,从断开后仍存在出边的那个端点出发,沿出边依次行走,直到到达另一端点为止。将途中依次经过的下标组成的序列记为 \(p\)。其中,\(p_i\) 表示经过的第 \(i\) 个下标(包含起点与终点)。根据定义,\(p\) 的长度为 \(\text{len}\)

在这一刻画下,沿置换环的访问顺序被完全转化为沿序列 \(p\) 的线性访问顺序。因此,对于固定的 \(x\),每完整走过一整圈置换环,其贡献恒为:

\[\sum_{i = 1}^{\text{len}} \min(A_x, B_{p_i}) \]

对于余下的步数的贡献,若其存在,则总是可以拆成 \(p\) 上的 \(1\)\(2\) 个区间,具体的:

\(k\) 为使得 \(p_i = x \bmod M\) 的唯一的 \(i\)\(r\)\(\text{step} \bmod \text{len}\);若 \(k + r - 1 \leq \text{len}\),则贡献为:

\[\sum_{i = k}^{k + r - 1} \min(A_x, B_{p_i}) \]

否则贡献可以拆分为:

\[\sum_{i = k}^{\text{len}} \min(A_x, B_{p_i}) + \sum_{i = 1}^{k + r - 1 - \text{len}} \min(A_x, B_{p_i}) \]

至此,问题被归约为:对若干区间,快速计算形如 \(\sum \min(A_x, B_{p_i})\) 的值。这是一个经典的二维偏序应用,因此整道题可以在 \(O((N + M) \log N)\) 的时间复杂度内解决。


参考实现:

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

using ll = long long;
using lll = __int128_t;

constexpr int N = 200000 + 1, P = 998244353;

int a[N], as[N], b[N], bel[N], pos[N], tr1[N];
ll tr2[N];
bitset<N> vis;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    ll k;
    cin >> n >> m >> k;

    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }
    memcpy(as, a, sizeof(int) * n);
    sort(as, as + n);
    int tota = unique(as, as + n) - as;

    for (int i = 0; i < m; ++i) {
        cin >> b[i];
    }

    int cnt = __gcd(n, m), len = m / cnt;
    vector<vector<int>> cyc(cnt);
    for (int i = 0, idx = 0; i < m; ++i) if (!vis[i]) {
        cyc[idx].reserve(len);
        for (int j = i; !vis[j]; j = (j + n) % m) {
            vis[j] = true;
            bel[j] = idx;
            pos[j] = cyc[idx].size();
            cyc[idx].push_back(b[j]);
        }
        ++idx;
    }

    vector<vector<vector<pair<int, ll>>>> que(cnt, vector<vector<pair<int, ll>>>(len));
    for (int i = 0, up = min<ll>(n, k); i < up; ++i) {
        int x = i % m;
        auto add = [&](int p, ll v) {
            que[bel[x]][p].emplace_back(a[i], v);
        };

        ll step = (k - i - 1) / n + 1, q = step / len;
        int r = step % len;
        add(len - 1, q);
        if (r) {
            if (pos[x] + r - 1 < len) {
                add(pos[x] + r - 1, 1);
                if (pos[x]) {
                    add(pos[x] - 1, -1);
                }
            } else {
                add(len - 1, 1);
                add(pos[x] - 1, -1);
                add(pos[x] + r - 1 - len, 1);
            }
        }
    }

    auto add = [&](int p, int v) {
        for ( ; p <= tota; p += p & -p) {
            ++tr1[p];
            tr2[p] += v;
        }
    };
    auto del = [&](int p, int v) {
        for ( ; p <= tota; p += p & -p) {
            --tr1[p];
            tr2[p] -= v;
        }
    };
    auto sum = [](int p) {
        int cnt = 0;
        ll sum = 0;
        for ( ; p; p &= p - 1) {
            cnt += tr1[p];
            sum += tr2[p];
        }
        return make_pair(cnt, sum);
    };

    auto getrk = [&](int x) {
        return lower_bound(as, as + tota, x) - as + 1;
    };

    lll ans = 0;
    for (int i = 0; i < cnt; ++i) {
        for (int j = 0; j < len; ++j) {
            add(a[j] = getrk(cyc[i][j]), cyc[i][j]);
            for (auto [qv, qx] : que[i][j]) {
                auto [ct, sm] = sum(getrk(qv));
                ans += (lll)qx * (sm + (ll)(j + 1 - ct) * qv);
            }
        }
        for (int j = 0; j < len; ++j) {
            del(a[j], cyc[i][j]);
        }
    }
    cout << (int)(ans % P) << '\n';
    return 0;
}

posted on 2026-01-10 15:06  SkyWave2022  阅读(3)  评论(0)    收藏  举报

导航