1 条题解

  • 0
    @ 2026-5-18 11:54:53

    题意

    给定一个长度为 nn 的排列 pp(下标从 11 开始)。
    定义一次“霸凌交换”如下:

    11. 找到最大的元素 pip_i 使得 piip_i \ne i(即最右边的不在正确位置的元素),记其下标为 ii22. 找到最小的元素 pjp_j 满足 j>ij > i(即在 ii 右边的最小元素),记其下标为 jj33. 交换 pip_ipjp_j

    重复执行直到排列变为 [1,2,,n][1,2,\dots,n]。记 f(p)f(p) 为所需的交换次数。
    初始时若已是恒等排列,则 f(p)=0f(p)=0

    现在有 qq 次更新,每次交换 pxp_xpyp_yx<yx<y),每次更新后输出新的 f(p)f(p)
    更新是持久化的,即每次交换会改变排列。


    关键结论

    经过分析,霸凌交换的过程等价于:

    • 每次操作会将当前最右边的错位元素(piip_i \ne i 中最大的 pip_i)放到它正确的位置,同时把右边最小的元素移到左边。
    • 最终,f(p)f(p) 可以分解为两部分:$$f(p) = \sum_{i: p_i > i} (p_i - i) + \sum_{i: p_i < i} 1 $$即:
      • 所有 pi>ip_i > i 的元素需要向右移动 piip_i - i
      • 所有 pi<ip_i < i 的元素每个贡献 11

    因此,我们只需要维护这两个部分的和。


    数据结构

    设:

    • ans=pi>i(pii)ans = \sum_{p_i > i} (p_i - i)
    • cnt=#{ipi<i}cnt = \#\{i \mid p_i < i\}

    f(p)=ans+cntf(p) = ans + cnt

    当交换 pxp_xpyp_yx<yx<y)时,只有位置 xxyy 的贡献会改变。
    我们可以用树状数组(BIT)来维护哪些位置满足 pi<ip_i < i(即这些位置在 BIT 中为 11,其余为 00),从而 cnt=BIT.sum(n)cnt = \text{BIT.sum}(n)

    同时,ansans 用一个变量直接维护。


    更新操作

    设交换前:

    • a=pxa = p_xb=pyb = p_y

    步骤 11:减去旧贡献

    对于位置 xx

    • a>xa > xansans 减去 axa - x
    • a<xa < x:BIT 在 xx 处减 11

    对于位置 yy

    • b>yb > yansans 减去 byb - y
    • b<yb < y:BIT 在 yy 处减 11

    步骤 22:交换值

    swap(p[x], p[y]);
    swap(pos[a], pos[b]);
    

    步骤 33:加上新贡献

    现在新的 px=bp_x = bpy=ap_y = a

    对于位置 xx

    • b>xb > xansans 加上 bxb - x
    • b<xb < x:BIT 在 xx 处加 11

    对于位置 yy

    • a>ya > yansans 加上 aya - y
    • a<ya < y:BIT 在 yy 处加 11

    步骤 44:输出答案

    f(p)=ans+BIT.sum(n)f(p) = ans + \text{BIT.sum}(n)


    复杂度

    • 预处理 O(n)O(n)
    • 每次更新 O(logn)O(\log n)(BIT 操作)
    • 总复杂度 O((n+q)logn)O((n+q) \log n),可过 n=5×105n=5\times10^5q=5×104q=5\times10^4

    完整代码

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    
    const int N = 5e5 + 5;
    
    int n, q;
    int p[N], pos[N];
    ll ans = 0;
    
    struct BIT {
        int tree[N];
        void add(int i, int x) {
            for (; i <= n; i += i & -i) tree[i] += x;
        }
        int sum(int i) {
            int s = 0;
            for (; i; i -= i & -i) s += tree[i];
            return s;
        }
        int range(int l, int r) {
            return sum(r) - sum(l - 1);
        }
    } bit;
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
    
        cin >> n >> q;
        for (int i = 1; i <= n; i++) {
            cin >> p[i];
            pos[p[i]] = i;
            if (p[i] > i) ans += p[i] - i;
            else if (p[i] < i) bit.add(i, 1);
        }
    
        auto update = [&](int x, int y) {
            // 交换 p[x] 和 p[y]
            int a = p[x], b = p[y];
            // 更新答案
            // 先减去旧贡献
            if (a > x) ans -= a - x;
            else if (a < x) bit.add(x, -1);
            if (b > y) ans -= b - y;
            else if (b < y) bit.add(y, -1);
    
            swap(p[x], p[y]);
            swap(pos[a], pos[b]);
    
            // 加上新贡献
            if (a > y) ans += a - y;
            else if (a < y) bit.add(y, 1);
            if (b > x) ans += b - x;
            else if (b < x) bit.add(x, 1);
        };
    
        while (q--) {
            int x, y;
            cin >> x >> y;
            if (x > y) swap(x, y);
            update(x, y);
            cout << ans + bit.sum(n) << "\n";
        }
    
        return 0;
    }
    

    示例验证

    输入:

    8 5
    6 2 1 5 3 4 7 8
    1 8
    2 3
    4 7
    7 8
    3 6
    

    输出:

    5
    6
    9
    8
    7
    

    与题目样例一致。


    总结

    • f(p)f(p) 转化为可维护的形式:$f(p) = \sum_{p_i > i} (p_i - i) + \#\{i \mid p_i < i\}$
    • 用变量 ansans 维护第一部分,用 BIT 维护第二部分
    • 每次交换只影响两个位置,O(logn)O(\log n) 更新
    • 1

    信息

    ID
    7196
    时间
    1000ms
    内存
    256MiB
    难度
    1
    标签
    递交数
    1
    已通过
    1
    上传者