USACO 2.2 Subset Sums 集合(subset)
Description
对于从1到N的连续整集合,能划分成两个子集合,且保证每个集合的数字和是相等的。
举个例子,如果N=3,对于{1,2,3}能划分成两个子集合,他们每个的所有数字和是相等的:
- {3} and {1,2}
这是唯一一种分法(交换集合位置被认为是同一种划分方案,因此不会增加划分方案总数)
如果N=7,有四种方法能划分集合{1,2,3,4,5,6,7},每一种分发的子集合各数字和是相等的:
- {1,6,7} and {2,3,4,5} {注 1+6+7=2+3+4+5}
- {2,5,7} and {1,3,4,6}
- {3,4,7} and {1,2,5,6}
- {1,2,4,7} and {3,5,6}
给出N,你的程序应该输出划分方案总数,如果不存在这样的划分方案,则输出0。程序不能预存结果直接输出。
好 废话不多说这是我在Ubuntu下打的第一个代码,个人认为Ubuntu下很多界面较好 风格也还行(又说废话了)。。。
这道题是一个dp 大致的意思是从1-n的每个数都给你一个 然后叫你找有多少种可能a+b+c...==x+y+z(当然a,b,c,x,y,都属于这n个数)同时 这些数都要用完而且不能重复用
刚刚看题目颇为不解 这用dp该怎么做 明显是坑爹嘛 后来实在想不出来就去baidu了(。。。) 然后看到一种普遍的解法就是看作一个01背包然后“
如果M=n*(n+1)/2是奇数,则没有分法。如果是偶数,背包容量为M/2,dp[k] += dp[k-i],(i=1,2,...,n)计算k的时候为避免重算,
需倒着进行。”(这不还是看不懂嘛(请原谅我的愚笨))
再后来一想[1,n]这个区间里所有数不久形成了一个an=n的等差数列嘛 那么根据求和公式sn=n+n*(n-1)/2 =n*(n+1)/2 既然这样 要使两边相等 那么
l(左边的和)=r(右边的和)=n*(n+1)/4 这样的话如果算出来的n*(n+1)/4为小数的话那么肯定就不可能有解了嘛 所以只要在开始判断一下n*(n+1)/4能不能除尽(即判断n*(n+1)%4是否为0) 然后如果除不尽就直接return 掉就行了。
在初步的判断完以后 我们就要开始用dp大法了 可以看作有n个物品 给你n*(n+1)/4的质量 这一次的分法就等于这一次j比i多出来的数的分法加上原来i的分法
代码如下:
1 #include<iostream> 2 using namespace std; 3 const int maxn=10000+10; 4 long long f[100000]; 5 int n,s; 6 int main() 7 { 8 cin>>n; 9 s=n*(n+1); 10 if(s%4!=0) 11 { 12 cout<<0<<endl; 13 return 0; 14 } 15 s/=4; 16 f[0]=1; 17 for(int i=1;i<=n;i++) 18 for(int j=s;j>=i;j--) 19 f[j]+=f[j-i]; 20 cout<<f[s]/2<<endl; 21 return 0; 22 }