把长度排序后,枚举当前木棍 x 作为选中集合里“最后一根最长木棍”。
这时只要数:前面的木棍能选出多少种方案,使长度和 > x。这个数量用 01 背包计数维护。
从“能拼成多边形”到“固定最长木棍,再用 01 背包数方案”。
把长度排序后,枚举当前木棍 x 作为选中集合里“最后一根最长木棍”。
这时只要数:前面的木棍能选出多少种方案,使长度和 > x。这个数量用 01 背包计数维护。
本题训练的不是一个单独技巧,而是一条建模链:
条件变形 → 固定最长木棍 → 排序避免重复 → 背包计数。
题目给出的条件是:
设最长木棍是 mx,其他木棍长度和是 rest,那么:
所以判断重点变成:其他木棍加起来,能不能超过最长木棍。
直接数一个集合很难,因为每个集合大小不同、最长木棍也不同。
我们固定“最长木棍”后,剩下的选择就只看长度和。这是很多计数题常用的切入点。
排序。
让“前面的木棍都不超过当前木棍”。
枚举当前木棍 x。
它负责统计以自己为最后最长木棍的方案。
查背包。
前面木棍的选法里,长度和 > x 的都能和 x 拼成多边形。
加入当前木棍。
它会成为后面更长木棍的“可选前缀”。
每个合法方案,只交给它里面“排序后下标最大的最长木棍”来统计。
如果最长长度出现多次,比如长度都是 2,也没关系。排序后它们仍然有先后位置,一个方案只由选中的最后那个最大值负责。
例子:方案 `{3, 8, 10}` 会在枚举 `10` 时被统计;方案 `{2, 2, 3}` 会在枚举排序后那根 `3` 时被统计。
样例 1 排序后仍是:
点击“下一步”,观察每次枚举最长木棍时,哪些长度和会被计入答案。
绿色格子表示:前面木棍能凑出的和已经大于当前最长木棍 x,所以可以和 x 组成合法多边形。
| 当前 x | 前面木棍 | 前面能凑出的长度和 | 大于 x 的方案数 | 累计答案 |
|---|---|---|---|---|
| 1 | 无 | 0 | 0 | 0 |
| 2 | 1 | 0, 1 | 0 | 0 |
| 3 | 1, 2 | 0, 1, 2, 3 | 0 | 0 |
| 4 | 1, 2, 3 | 0, 1, 2, 3×2, 4, 5, 6 | 2 | 2 |
| 5 | 1, 2, 3, 4 | 0, 1, 2, 3×2, 4×2, 5×2, 6×2, 7×2, 8, 9, 10 | 7 | 9 |
表里的 `3×2` 表示长度和为 3 的选法有 2 种:选 `{3}` 或选 `{1,2}`。
本题数的是“下标集合”的方案数,所以相同长度的木棍如果下标不同,也要当作不同选择。
如果当前最长木棍长度是 10,其他木棍长度和至少要满足什么?
为什么背包更新要倒序循环?
为什么可以把超过 5000 的长度和都记到 5001?
#include <bits/stdc++.h>
using namespace std;
const int MOD = 998244353;
const int LIM = 5001; // 5001 表示长度和已经超过 5000
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(a.begin(), a.end());
vector<int> dp(LIM + 1, 0);
dp[0] = 1;
int ans = 0;
for (int x : a) {
// 当前木棍作为最长木棍,前面选出的总长度必须大于 x。
for (int s = x + 1; s <= LIM; s++) {
ans += dp[s];
if (ans >= MOD) ans -= MOD;
}
// 01 背包:把当前木棍加入后续可选集合。
for (int s = LIM; s >= 0; s--) {
int to = min(LIM, s + x);
dp[to] += dp[s];
if (dp[to] >= MOD) dp[to] -= MOD;
}
}
cout << ans << '\n';
return 0;
}
P1048 采药
练 01 背包的倒序更新。
P1164 小A点菜
练“不是求最大值,而是数方案数”。
P14360 多边形
练排序后固定最大值,再用背包统计前缀方案。