T1 称重(weight)题解
考场策略 :第一题数据范围 \(n \le 2\times10^5\),应优先尝试贪心 。如果一时想不到正确贪心,可以先写DP拿 65 分,再根据特殊性质往上加。
题面
有 \(n\) 件物品排成一行,每件重量为 \(0,H\) 内的整数。有 \(n-1\) 条称重记录 \(b_i\),表示第 \(i\) 件和第 \(i+1\) 件的重量之和为 \(b_i\)。记录可能矛盾,可以删除一些记录,使得剩下的记录都能成立。求最少删除多少条记录。
思路
核心观察 :如果连续的一段记录都保留,那么每件物品的可行重量构成一个连续区间。
假设当前物品的可行重量是区间 \(L, R\),加入记录 \(b_i\) 后,下一件物品的重量为 \(b_i - w_i\),所以可行区间变为 \(b_i-R, b_i-L\),再与 \(0, H\) 取交集:
\(L' = \max(0, b_i - R), \quad R' = \min(H, b_i - L)\)
如果 \(L' > R'\),说明这条记录无法与前面的记录同时保留,必须删除。
贪心策略 :从左到右扫描,维护当前连续段的可行重量区间。遇到矛盾就删除当前记录(答案 +1),并重置区间为 \(0, H\),从下一件物品开始新的一段。
正确性证明(调整法 ( 邻项微扰 )) :任取一个最优方案,考虑第一段从 \(s\) 开始,\(b_t\) 是第一个出现矛盾的位置。原方案在 \(s, t\) 中至少删一条记录。我们可以把这段内的删除全部替换成只删 \(b_t\):
- 左侧:\(b_s, \dots, b_{t-1}\) 全部保留(因为 \(t\) 是第一个矛盾位置,它们一定相容)
- 右侧:删除 \(b_t\) 后,物品 \(t+1\) 与左侧断开约束,原方案右侧仍然成立
这样调整后删除数不会增加,且方案仍然合法。因此存在一个最优解,每次删除都发生在第一次矛盾的位置,这正是贪心算法的行为。
AC code
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 5;
const ll INF = 1e18;
ll n, H;
ll b[N];
int main () {
freopen("weight.in", "r", stdin);
freopen("weight.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> H;
ll L = 0, R = H;
ll ans = 0;
for (int i = 1; i < n; i ++) {
ll b; cin >> b;
ll nL = max(0ll, b - R);
ll nR = min(H, b - L);
if (nL <= nR) {
L = nL;
R = nR;
}
else {
ans ++;
L = 0;
R = H;
}
}
cout << ans << '\n';
return 0;
}
复杂度分析
- 时间复杂度 :\(O(n)\),每条记录只处理一次
考场思路与得分路径
| 得分 | 算法 | 适用范围 |
|---|---|---|
| 10 分 | 枚举所有重量 | \(n \le 5, H = 1\) |
| 20 分 | 枚举删法 + 区间判定 | \(n \le 20\) |
| 30 分 | 重量 DP \(O(nH^2)\) | \(n, H \le 200\) |
| 40 分 | 重量 DP 优化 \(O(nH)\) | \(n, H \le 2000\) |
| 65 分 | 分段 DP \(O(n^2)\) | \(n \le 3000\) |
| 70 分 | + 特判 H=1 | \(n \le 2\times10^5, H=1\) |
| 75 分 | + 特判 H≤2 | \(n \le 2\times10^5, H\le2\) |
| 100 分 | 可行区间 + 贪心 | 全部数据 |