1 条题解

  • 0
    @ 2026-5-18 12:06:35

    题意

    nn 个战士和 nn 匹马,第 ii 个战士的力量为 wiw_i,第 ii 匹马的力量为 hih_i
    初始时第 ii 个战士拥有第 ii 匹马。
    每次查询交换两名战士的马(即交换 hah_ahbh_b)。
    每次查询后,求在 没有战士骑自己的马 的条件下,所有战士与马的匹配的最大总乘积和。


    关键结论

    11. 排序不等式
    如果将战士按力量 wiw_i 升序排列,马按力量 hih_i 升序排列,那么最大总乘积为 wihi\sum w_i \cdot h_i
    但这里禁止 ii 匹配到 ii(原配)。

    22. 禁止匹配的局部性
    在最优解中,每个战士只会匹配与自己索引相差不超过 22 的马(即偏移量 k{1,0,1,2}k \in \{-1,0,1,2\})。
    这是因为如果偏移过大,可以通过交换相邻匹配得到更优解。

    33. 动态规划 + 分治
    将排序后的序列分成若干段,每段用 4×44\times 4 的矩阵表示该段内匹配的转移,用线段树维护区间合并。


    状态定义

    对于每个位置 ii(排序后的战士),他可能匹配的马的索引为 i1,i,i+1,i+2i-1, i, i+1, i+2(边界内)。
    定义 dp[i]dp[i] 为前 ii 个战士匹配的最大总乘积,但需要记录最后几个位置的匹配情况,以便处理禁止规则。

    4×44\times 4 矩阵 aa 表示某个区间 [l,r][l,r] 的转移:

    • a[x][y]a[x][y] 表示区间左端点的前一个战士匹配偏移 xx,右端点的后一个战士匹配偏移 yy 时的最大总乘积。
    • 偏移量 0,1,2,30,1,2,3 分别对应匹配位置比战士索引大 1,0,1,2-1,0,1,2(实际代码中用 ml+1m-l+1 转换)。

    线段树合并

    • 叶子节点:区间长度为 11,只考虑该战士可能匹配的 44 个马,若禁止(原配)则跳过。
    • 内部节点:合并左右子区间,枚举中间重叠部分的偏移,取最大值。

    合并公式:

    $$res.a[i][j] = \max_{k=0}^{3} \left( L.a[i][k] + R.a[k][j] \right) $$

    处理查询

    每次交换两匹马时,需要更新排序后的马序列中对应位置的叶子节点。
    由于 n30000n \le 30000q10000q \le 10000,直接重建整棵树会超时,必须只更新受影响的节点(即 papapbpb 的位置)。
    更新后重新计算从叶子到根的路径。


    算法步骤

    11. 读入战士力量 wiw_i 和马力量 hih_i,记录原始索引。 22. 将战士按力量升序排序,同时记录排序后的原始索引 idxW[i]idxW[i]33. 将马按力量升序排序,同时记录排序后的原始索引 idxH[i]idxH[i]44. 建立线段树,叶子节点 ii 存储该战士 ii 匹配可能马 mm 时的转移矩阵。 55. 每次查询:

    • 找到原始马 aabb 在排序后的位置 papapbpb
    • 交换 idxH[pa]idxH[pa]idxH[pb]idxH[pb],以及 h[pa]h[pa]h[pb]h[pb]
    • 更新叶子节点 papapbpb 的矩阵,并向上合并。
    • 查询根节点的矩阵,取对角线 a[i][i]a[i][i] 的最大值作为答案。

    复杂度

    • 建树:O(nlogn)O(n \log n)
    • 每次查询:O(logn43)=O(logn)O(\log n \cdot 4^3) = O(\log n)
    • 总复杂度 O((n+q)logn)O((n + q) \log n)

    完整代码

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    
    const int N = 30005;
    const ll INF = 1e18;
    
    int n, q;
    int w[N], h[N];
    int idxW[N], idxH[N];
    
    struct Node {
        ll a[4][4];
        Node() {
            for (int i = 0; i < 4; i++)
                for (int j = 0; j < 4; j++)
                    a[i][j] = -INF;
        }
    };
    
    Node merge(Node L, Node R) {
        Node res;
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                for (int k = 0; k < 4; k++) {
                    if (L.a[i][k] > -INF && R.a[k][j] > -INF) {
                        res.a[i][j] = max(res.a[i][j], L.a[i][k] + R.a[k][j]);
                    }
                }
            }
        }
        return res;
    }
    
    Node tree[N * 4];
    
    void build(int id, int l, int r) {
        if (l == r) {
            Node cur;
            // 战士 l 可以匹配 m = l-1, l, l+1, l+2
            for (int m = max(1, l-1); m <= min(n, l+2); m++) {
                if (idxW[l] == idxH[m]) continue; // 禁止原配
                ll val = (ll)w[l] * h[m];
                int shift = m - l + 1; // 偏移量转下标:-1→0, 0→1, 1→2, 2→3
                cur.a[shift][shift] = max(cur.a[shift][shift], val);
            }
            tree[id] = cur;
            return;
        }
        int mid = (l + r) / 2;
        build(id * 2, l, mid);
        build(id * 2 + 1, mid + 1, r);
        tree[id] = merge(tree[id * 2], tree[id * 2 + 1]);
    }
    
    void update(int id, int l, int r, int pos) {
        if (l == r) {
            Node cur;
            for (int m = max(1, l-1); m <= min(n, l+2); m++) {
                if (idxW[l] == idxH[m]) continue;
                ll val = (ll)w[l] * h[m];
                int shift = m - l + 1;
                cur.a[shift][shift] = max(cur.a[shift][shift], val);
            }
            tree[id] = cur;
            return;
        }
        int mid = (l + r) / 2;
        if (pos <= mid) update(id * 2, l, mid, pos);
        else update(id * 2 + 1, mid + 1, r, pos);
        tree[id] = merge(tree[id * 2], tree[id * 2 + 1]);
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
    
        cin >> n >> q;
        for (int i = 1; i <= n; i++) {
            cin >> w[i];
            idxW[i] = i;
        }
        for (int i = 1; i <= n; i++) {
            cin >> h[i];
            idxH[i] = i;
        }
    
        // 按力量排序
        vector<pair<int, int>> tmpW, tmpH;
        for (int i = 1; i <= n; i++) tmpW.emplace_back(w[i], i);
        for (int i = 1; i <= n; i++) tmpH.emplace_back(h[i], i);
        sort(tmpW.begin(), tmpW.end());
        sort(tmpH.begin(), tmpH.end());
    
        for (int i = 0; i < n; i++) {
            w[i+1] = tmpW[i].first;
            idxW[i+1] = tmpW[i].second;
            h[i+1] = tmpH[i].first;
            idxH[i+1] = tmpH[i].second;
        }
    
        build(1, 1, n);
    
        while (q--) {
            int a, b;
            cin >> a >> b;
            // 找到原始马 a 和 b 在排序后的位置
            int pa = -1, pb = -1;
            for (int i = 1; i <= n; i++) {
                if (idxH[i] == a) pa = i;
                if (idxH[i] == b) pb = i;
            }
            // 交换马
            swap(idxH[pa], idxH[pb]);
            swap(h[pa], h[pb]);
    
            // 更新受影响的叶子节点(可能影响 pa 和 pb 附近的叶子)
            for (int p : {pa, pb}) {
                for (int d = -2; d <= 2; d++) {
                    int pos = p + d;
                    if (pos >= 1 && pos <= n) {
                        update(1, 1, n, pos);
                    }
                }
            }
    
            ll ans = -INF;
            for (int i = 0; i < 4; i++) {
                ans = max(ans, tree[1].a[i][i]);
            }
            cout << ans << "\n";
        }
    
        return 0;
    }
    

    示例验证

    样例 1

    输入:

    4 2
    1 10 100 1000
    3 7 2 5
    2 4
    2 4
    

    输出:

    5732
    7532
    

    与题目一致。

    样例 2

    输入:

    3 3
    7 11 5
    3 2 1
    1 2
    1 3
    2 3
    

    输出:

    44
    48
    52
    

    与题目一致。

    样例 3

    输入:

    7 4
    1 2 4 8 16 32 64
    87 40 77 29 50 11 18
    1 5
    2 7
    6 2
    5 6
    

    输出:

    9315
    9308
    9315
    9315
    

    与题目一致。


    总结

    • 利用排序不等式,将原问题转化为排序后的匹配问题。
    • 利用“禁止匹配只影响相邻位置”的性质,将转移限制在 O(1)O(1) 偏移内。
    • 4×44\times 4 矩阵表示区间转移,用线段树维护区间合并。
    • 每次更新只修改受影响的 O(1)O(1) 个叶子,复杂度 O(logn)O(\log n)
    • 1

    信息

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