1 条题解
-
0
题意
有 个战士和 匹马,第 个战士的力量为 ,第 匹马的力量为 。
初始时第 个战士拥有第 匹马。
每次查询交换两名战士的马(即交换 和 )。
每次查询后,求在 没有战士骑自己的马 的条件下,所有战士与马的匹配的最大总乘积和。
关键结论
. 排序不等式
如果将战士按力量 升序排列,马按力量 升序排列,那么最大总乘积为 。
但这里禁止 匹配到 (原配)。. 禁止匹配的局部性
在最优解中,每个战士只会匹配与自己索引相差不超过 的马(即偏移量 )。
这是因为如果偏移过大,可以通过交换相邻匹配得到更优解。. 动态规划 + 分治
将排序后的序列分成若干段,每段用 的矩阵表示该段内匹配的转移,用线段树维护区间合并。
状态定义
对于每个位置 (排序后的战士),他可能匹配的马的索引为 (边界内)。
定义 为前 个战士匹配的最大总乘积,但需要记录最后几个位置的匹配情况,以便处理禁止规则。用 矩阵 表示某个区间 的转移:
- 表示区间左端点的前一个战士匹配偏移 ,右端点的后一个战士匹配偏移 时的最大总乘积。
- 偏移量 分别对应匹配位置比战士索引大 (实际代码中用 转换)。
线段树合并
- 叶子节点:区间长度为 ,只考虑该战士可能匹配的 个马,若禁止(原配)则跳过。
- 内部节点:合并左右子区间,枚举中间重叠部分的偏移,取最大值。
合并公式:
$$res.a[i][j] = \max_{k=0}^{3} \left( L.a[i][k] + R.a[k][j] \right) $$
处理查询
每次交换两匹马时,需要更新排序后的马序列中对应位置的叶子节点。
由于 ,,直接重建整棵树会超时,必须只更新受影响的节点(即 和 的位置)。
更新后重新计算从叶子到根的路径。
算法步骤
. 读入战士力量 和马力量 ,记录原始索引。 . 将战士按力量升序排序,同时记录排序后的原始索引 。 . 将马按力量升序排序,同时记录排序后的原始索引 。 . 建立线段树,叶子节点 存储该战士 匹配可能马 时的转移矩阵。 . 每次查询:
- 找到原始马 和 在排序后的位置 和 。
- 交换 和 ,以及 和 。
- 更新叶子节点 和 的矩阵,并向上合并。
- 查询根节点的矩阵,取对角线 的最大值作为答案。
复杂度
- 建树:
- 每次查询:
- 总复杂度
完整代码
#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与题目一致。
总结
- 利用排序不等式,将原问题转化为排序后的匹配问题。
- 利用“禁止匹配只影响相邻位置”的性质,将转移限制在 偏移内。
- 用 矩阵表示区间转移,用线段树维护区间合并。
- 每次更新只修改受影响的 个叶子,复杂度 。
- 1
信息
- ID
- 7201
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 1
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者