[USACO2.2] 集合 Subset Sums
把1..N分成和相等的两堆→0/1背包计数dp[target],总和奇数直接0,最后结果除以2去重。
OJ: luogu
题目 ID: P1466
难度:普及-
标签:动态规划01背包计数
日期: 2026-08-08 23:13
题意
把集合
思路
一句话本质:把
两个子集和相等,意味着什么约束?
设数列总和
如果
设
于是问题变成:从
计数怎么用 DP 做?
这是一个标准的 0/1 背包计数问题。设
初始化
算出
因为上述过程算出来的是"选出一个和为
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXS = 400;
int n;
// dp[j] 表示从 1..n 中选若干不同的数,和为 j 的子集个数。
ll dp[MAXS * MAXS];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
int total = n * (n + 1) / 2;
if (total % 2 != 0) { // 总和为奇数无法平分为两个相等子集
cout << 0 << '\n';
return 0;
}
int target = total / 2;
// 0/1 背包计数:dp[0]=1,每个数 i 只能用一次,倒序枚举。
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = target; j >= i; j--) {
dp[j] += dp[j - i];
}
}
// 每种集合划分算了两次(左右交换),除以 2。
cout << dp[target] / 2 << '\n';
return 0;
}复杂度
- 时间复杂度:
, 时 ,完全可行 - 空间复杂度:
总结
本题的关键翻译是:“分成两个和相等的子集” → “找一个和为