Description
给定一个正整数的集合A={a1,a2,….,an},是否可以将其分割成两个子集合,使两个子集合的数加起来的和相等。例A = { 1, 3, 8, 4, 10} 可以分割:{1, 8, 4} 及 {3, 10}
Input
第一行集合元素个数n n <=300 第二行n个整数
Output
如果能划分成两个集合,输出任意一个子集,否则输出“no”
Sample Input
5
1 3 8 4 10
Sample Output
3 10
解题思路
- 本题可转化为一个容量为sum/2的背包,是否存在一组物品可以刚好装满背包的问题。
- 用dp[i][j]来表示前i个物品是否能恰好装满容量为j的背包,即可得递推公式:
dp[i][j] = if(dp[i-1][j] || dp[i-1][j-v[i]]) // j>=v[i]
- 路径输出:倒序输出,若前i个物品能恰好装满j容量的背包,并且前i-1个物品不能,则说明第i个物品必须装入背包,即:
if(j >= a[i] && dp[i][j] && !dp[i-1][j]) printf("%d ", a[i]);
然后再让容量j-=a[i]继续输出剩余物品
代码
#include<bits/stdc++.h>
using namespace std;
int a[305];
int main () {
int n, sum = 0;
cin >> n;
for(int i = 1; i <= n; ++i) {
scanf("%d", a+i);
sum += a[i];
}
if(sum%2) { // 和为奇数直接输出no
cout << "no";
return 0;
}
sum/=2;
int dp[n+5][sum+5];
memset(dp, 0, sizeof dp);
dp[1][0] = 1; // 这里不能少
dp[1][a[1]] = 1; // 同上
for (int i = 2; i <= n; ++i) { // 枚举物品
for (int j = 0; j <= sum; ++j) { // 枚举容量
if(dp[i-1][j]) {
dp[i][j] = 1;
}
if(j >= a[i] && dp[i-1][j-a[i]]) {
dp[i][j] = 1;
//cout << "debug:" << i << " " << j;
}
}
}
if(!dp[n][sum]) { // 没有找到可行方案
printf("no");
return 0;
}
// 路径输出,倒序
for (int i = n, j = sum; i >= 1&&j>0; --i) {
if(j >= a[i] && dp[i][j] && !dp[i-1][j]) {
printf("%d ", a[i]);
j -= a[i];
}
}
return 0;
}