1 条题解

  • 0
    @ 2026-10-24 11:21:12

    练琴记录:解法与部分分

    先把“一轮弹很多遍”拆开.设本轮前缀为 P=a1,…,axP=a_1,\ldots,a_x,重复 cc 遍.记:

    • IxI_x:一份 PP 内部的逆序对数.
    • DxD_x:两份 PP 按先后拼接时,前一份到后一份的逆序对数.
    • HxH_x:已有全部历史记录到一份 PP 的逆序对数.

    新增逆序对分为历史到本轮、同一遍内部、本轮不同遍之间,分别贡献

    cHx,cIx,(c2)Dx.cH_x,\qquad cI_x,\qquad \binom c2D_x.

    DxD_x 等于前缀里不同位置且值不同的无序对数量:两份前缀里,一大一小的两个值必有一个方向满足大于.相等的不计.I,DI,D 都能用按值计数的树状数组预处理.

    剩下的难点只有:前缀会变,历史还在,怎样维护 HxH_x?

    先固定历史,移动前缀端点

    设历史中各音高出现次数已经可以查询.前缀新增一个音符 aja_j 时,HH 增加“历史中严格大于 aja_j 的音符数”;前缀删去末尾音符时,减去同一个数.这里删的是本轮准备练的前缀,不是历史记录.

    由于每一轮前缀最多增长一位,全部向右移动不超过 nn 次;向左移动总数不超过向右移动总数.所以可以逐位处理端点移动,而不必寻找一次删除整段的复杂结构.

    再固定前缀,追加整轮练习

    把曲谱位置 jj 在历史中出现的次数写成

    hj=bj+z[j≤x].h_j=b_j+z[j\le x].

    zz 是当前练习前缀共同携带的增量,bjb_j 是各位置的补偿值.树状数组按音高维护两种和:补偿值之和、当前前缀内的位置数.因此历史里大于 vv 的总次数为

    ∑aj>vbj+z#{j≤x:aj>v}.\sum_{a_j>v} b_j+z\#\{j\le x:a_j>v\}.

    前缀扩展到位置 jj 时,让 bj←bj−zb_j\gets b_j-z,再把它加入活动前缀;缩短移出它时,让 bj←bj+zb_j\gets b_j+z,再移出活动前缀.两种补偿都保证真实历史次数 hjh_j 没变.这也解释了为什么退出后重新进入的音符不会丢掉旧记录.

    现在追加 cc 遍,只需要让 z←z+cz\gets z+c.历史到当前前缀的逆序对数同时增加 cDxcD_x,所以更新 H←H+cDxH\gets H+cD_x.

    处理顺序为:先移动端点并修正 HH,再用旧的 HH 计算本轮新增答案,最后更新 z,Hz,H.

    预处理与每次单点维护均为 O(log⁡n)O(\log n),端点总移动 O(n)O(n),总时间 O(nlog⁡n)O(n\log n),空间 O(n)O(n).次数、组合数和树状数组都可以按模数维护,因为只用于加乘,不用它们比较大小;比较的是原音高.

    部分分

    子任务 正确做法 复杂度
    1 展开记录,逐对比较 O(L2)O(L^2)
    2 展开记录,归并排序或树状数组统计逆序对 O(Llog⁡L)O(L\log L)
    3 每轮按音高直接重建历史频数和后缀次数,扫描本轮前缀得到 HH,再逐位置增加 cc;I,DI,D 预处理 O(n2+nlog⁡n)O(n^2+n\log n)
    4 只有 0、1,只记历史 1 的数量以及当前前缀中 0、1 的数量;跨份贡献为两者乘积 O(n)O(n)
    5 前缀只增长且每次一位.维护一个总遍数 zz,第 jj 位的历史次数为 z−zj−1z-z_{j-1};无需处理退出和再进入 O(nlog⁡n)O(n\log n)
    6 补偿值维护任意缩短,使用上述完整算法

    其中 LL 只用于描述展开长度,完整解法不需要实际计算或分配 LL 个位置.

    参考程序

    #include <algorithm>
    #include <iostream>
    #include <vector>
    
    using namespace std;
    using ll = long long;
    const ll mod = 998244353;
    const ll inv2 = (mod + 1) / 2;
    
    ll norm(ll x) {
        x %= mod;
        if (x < 0) x += mod;
        return x;
    }
    
    struct BIT {
        int n;
        vector<ll> t;
        explicit BIT(int n) : n(n), t(n + 1) {}
        void add(int x, ll v) {
            v = norm(v);
            for (; x <= n; x += x & -x) t[x] = (t[x] + v) % mod;
        }
        ll sum(int x) const {
            ll res = 0;
            for (; x; x -= x & -x) res = (res + t[x]) % mod;
            return res;
        }
        ll greater(int x) const {
            return norm(sum(n) - sum(x));
        }
    };
    
    ll solve(const vector<int>& a, const vector<pair<int, ll>>& ops) {
        int n = (int)a.size() - 1;
        vector<int> val(a.begin() + 1, a.end()), rank(n + 1);
        sort(val.begin(), val.end());
        val.erase(unique(val.begin(), val.end()), val.end());
        int m = (int)val.size();
        for (int i = 1; i <= n; i++)
            rank[i] = lower_bound(val.begin(), val.end(), a[i]) - val.begin() + 1;
    
        vector<ll> inside(n + 1), unequal(n + 1);
        vector<int> freq(m + 1);
        BIT prep(m);
        for (int i = 1; i <= n; i++) {
            inside[i] = (inside[i - 1] + prep.greater(rank[i])) % mod;
            unequal[i] = (unequal[i - 1] + i - 1 - freq[rank[i]]) % mod;
            freq[rank[i]]++;
            prep.add(rank[i], 1);
        }
    
        BIT base(m), active(m);
        ll z = 0, h = 0, ans = 0;
        int cur = 0;
        auto greater = [&](int v) {
            return (base.greater(v) + z * active.greater(v)) % mod;
        };
        for (auto op : ops) {
            int x = op.first;
            ll c = op.second % mod;
            while (cur < x) {
                ++cur;
                int v = rank[cur];
                h = (h + greater(v)) % mod;
                base.add(v, -z);
                active.add(v, 1);
            }
            while (cur > x) {
                int v = rank[cur];
                h = norm(h - greater(v));
                base.add(v, z);
                active.add(v, -1);
                --cur;
            }
            ll choose = c * norm(c - 1) % mod * inv2 % mod;
            ans = (ans + c * h + c * inside[x]) % mod;
            ans = (ans + choose * unequal[x]) % mod;
            z = (z + c) % mod;
            h = (h + c * unequal[x]) % mod;
        }
        return ans;
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
    #ifdef SELF_TEST_BATCH
        int cases;
        cin >> cases;
        while (cases--) {
    #endif
            int n;
            if (!(cin >> n)) return 0;
            vector<int> a(n + 1);
            for (int i = 1; i <= n; i++) cin >> a[i];
            vector<pair<int, ll>> ops(n);
            for (auto& op : ops) cin >> op.first >> op.second;
            cout << solve(a, ops) << '\n';
    #ifdef SELF_TEST_BATCH
        }
    #endif
    }
    
    • 1

    信息

    ID
    830
    时间
    3000ms
    内存
    512MiB
    难度
    7
    标签
    递交数
    8
    已通过
    0
    上传者