【01背包】P1466 [USACO2.2] 集合 Subset Sums
题目
P1466 [USACO2.2] 集合 Subset Sums
分析
感觉本题最难的难点就是想出用01背包做。特征:1~n的连续整数集合中挑选某些数。要求两个子集合的数字和是相等的,就相当于给定了限制条件在1~n中挑数字。
先分析状态表示,状态表示一般跟着题目要求什么来设置。本题要求划分方案数,那么我们的状态表示就设置成方案数。
f[i][j]:从前 i 个数中选出数值和为 j 的方案数
但是注意,题目要求的实际上是划分成两个子集合这样的方案有多少个,而我们的状态表示最终求的是有多少个数值和为sum/2的子集合,所以最后结果要除以2。
状态转移方程:
方案数 = 不选 + 选
f[i][j] = f[i-1][j] + f[i-1][j-i]
代码实现上,如果写的是二维的状态转移方程,由于“选”是有条件限制的,所以“选”和“不选”是分开讨论的。
如果写成一维的,由于将条件省略进了循环中,就可以写在一起。
重要:当状态表示为方案数的时候,不要忘记初始化!!!
AC代码
#include<iostream>using namespace std;typedef long long LL; int n;LL f[40][800]; //f[i][j]:从前i个数中选出数值和为j的方案数 int main()
{cin >> n;int sum = (1 + n) * n / 2; //等差数列求和 if(sum % 2 == 1) //总和是奇数就凑不出来两堆数值相同的 {cout << 0;return 0;}sum /= 2; //sum/2作为限制条件 f[0][0] = 1; //当状态表示为方案数的时候,不要忘记初始化!!! for(int i=1;i<=n;i++){for(int j=0;j<=sum;j++){f[i][j] += f[i-1][j]; //不选 if(j >= i) f[i][j] += f[i-1][j-i]; //选 }} cout << f[n][sum] / 2; //题目问的是可以分成多少对 return 0;
}