USACO 2.2 Subset Sums 【经典的方案DP+必要的转化】
题目在这里:https://www.luogu.org/problem/show?pid=1466#sub
1、把一个连续正整数集合平分,假设该正整数集合和为s,那么平分后的两个集合和均为s/2,因此我们首先把s是奇数的n排除掉
2、接着,我们发现:平分集合方案数======》用n个数凑s/2的方案数======》DP
3、若用f[i][j]表示用前i 个数凑j 的方案,则 f[i][j]=f[i-1][j]+f[i-1][j-i] (1<=i<=n,0<=j<=s/2)
4、当然,二维可以压成一维,代码如下:
二维
-------------------------------------------------------
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int n; long long f[50][5050]={0};//how many methods are there to make up j with the first i numbers int main() { freopen("subset.in","r",stdin); freopen("subset.out","w",stdout); cin>>n; if(n==0||n*(n+1)%4!=0) { cout<<"0"<<endl; return 0; } int s=n*(n+1)/2; s/=2; f[1][1]=1; f[1][0]=1; for(int i=2;i<=n;i++) for(int j=s;j>=0;j--)//分两种情况:i能拼起j(取i或不取), 或不能(只能不取) if(j>=i) f[i][j]=f[i-1][j-i]+f[i-1][j]; else f[i][j]=f[i-1][j]; cout<<f[n][s]/2<<endl; return 0; }
一维
------------------------------------------------------------------
#include<cstdio> int n,s; long long f[400]={0}; int main() { scanf("%d",&n); s=n*(n+1)/2; if (s&1) { printf("0"); return 0; } s/=2; f[0]=1; for(int i=1;i<=n;i++) for(int j=s;j>=i;j--) f[j]+=f[j-i]; printf("%d\n",(long long)f[s]/2); return 0; }