P14269 兄妹
题解:P14259 兄妹(siblings)
题目大意
书店有若干排书架,第
- 在同一排内,可以上下移动。
- 在不同排之间,只能通过出入口移动。
求两人中用时较长者的最短用时。
解题思路
关键观察
-
每排的最远距离:对于同一排
r ,如果有多本书,只需要考虑最远的那本,因为在同一排内移动可以顺便放其他书。设x_r = \max\{c_i \mid r_i = r\} ,如果该排没有书,则x_r = 0 。 -
总用时计算:如果一个人负责的集合
A 包含排号r_1, r_2, \dots, r_k ,那么他的用时为:
因为需要走到最远的排,并且每排都需要来回。
- 问题转化:设最大排号为
m 。我们需要将排1, 2, \dots, m 分成两个集合Y 和S ,使得:
最小。由于
那么问题转化为:求
其中
- 动态规划:设总距离和
c = \sum_{i=1}^m x_i 。我们枚举s (即S 的最大排号),但更高效的方法是使用背包 DP 计算所有可能的子集和。
设
则用时为
算法步骤
- 预处理每排的
x_r 。 - 找到最大排号
m 。 - 计算总距离和
c = \sum_{r=1}^m x_r 。 - 初始化背包数组
f ,大小为c+1 ,f[0] = 1 。 - 初始化答案
ans 为一个较大值。 - 对于
i 从1 到m :- 如果
x_i > 0 ,则从后往前更新背包:对于j 从c 到0 ,如果f[j] 为真,则设置f[j + x_i] 为真。 - 然后遍历所有
j 从0 到c ,如果f[j] 为真,则计算t = \max(i + j, m + c - j) ,并更新ans = \min(ans, t) 。
- 如果
- 输出
2 \times ans 。
复杂度分析
- 时间复杂度:
O(m \cdot c) ,其中m 是最大排号,c 是总距离和。由于m \le 500 ,c \le 500 \times 500 = 250000 ,但实际中c 可能较小,因为每排的x_r 不会都很大。 - 空间复杂度:
O(c) ,使用一维背包数组。
代码实现
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAX_R = 500;
const int MAX_SUM = 250000;
int x[MAX_R + 1];
bool f[MAX_SUM + 1];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin >> T;
while (T --) {
memset(x, 0, sizeof(x));
memset(f, 0, sizeof(f));
int n;
cin >> n;
int max_r = 0;
for (int i = 0; i < n; i ++) {
int r, c;
cin >> r >> c;
x[r] = max(x[r], c);
max_r = max(max_r, r);
}
int r = max_r;
while (r > 0 && x[r] == 0) r --;
int total_c = 0;
for (int i = 1; i <= r; i++) {
total_c += x[i];
}
f[0] = true;
int ans = 300000;
int s = 0;
for (int i = 1; i <= r; i ++) {
if (x[i] == 0) continue;
for (int j = s; j >= 0; j --) {
if (f[j]) {
f[j + x[i]] = true;
}
}
s += x[i];
for (int j = 0; j <= s; j ++) {
if (f[j]) {
ans = min(ans, max(i + j, r + total_c - j));
}
}
}
cout << ans * 2 << '\n';
}
return 0;
}