题目描述
对于从1到N (1 <= N <= 39) 的连续整数集合,能划分成两个子集合,且保证每个集合的数字之和是相等的。
举个例子,如果N=3,对于{1,2,3}能划分成两个子集合,他们每个的所有数字和是相等的:
{3} 和 {1,2}
这是唯一一种分法(交换集合位置被认为是同一种划分方案,因此不会增加划分方案总数)
如果N=7,有四种方法能划分集合{1,2,3,4,5,6,7},每一种分发的子集合各数字和是相等的:
{1,6,7} 和 {2,3,4,5} 1+6+7=2+3+4+5
{2,5,7} 和 {1,3,4,6}
{3,4,7} 和 {1,2,5,6}
{1,2,4,7} 和 {3,5,6}
给出N,你的程序应该输出划分方案总数,如果不存在这样的划分方案,则输出0。
输入
第1行:一个整数N
输出
第1行:输出划分方案总数,如果不存在则输出0。
样例输入
7
样例输出
4
题目分析
这道题作者的第一直觉现在想都觉得很蠢很天真啊,我一看到“集合”,就默默地打上了: #include
(注:C++ STL特有变量类型头文件-集合)。但是作者很快就发现,其实这道题跟 set 一点关系都没有。。。
然后作者就想到了搜索(深度优先搜索)。想都没想就开始打代码了。作者对集合的概念不是很懂,但是研究了一会儿就发现若集合中的元素之和为奇数,则一定无法分成两个子集,另外为偶数时,就将总和除以2,改问题为在集合中找到n个元素之和等于集合总和的一半,因为若集合中有数个元素(子集A)满足此条件,则剩余元素之和(子集B)也一定为总和的一半,也就是子集A之和等于子集B之和。
接下来作者开始写在集合中找元素等于集合元素总和的一半的函数(参数表:sum当前元素之和,put正在判断的元素)。道理比较简单,一个元素只有两种情况,选和不选。因此我在函数内自调用,选用则是:sum+元素[put],put+1;不选用则是:sum,put+1。还要设置边界:1.put超出元素总量;2.sum正好是子集总和的一半。
但是这样是将所有情况都枚举完了,答案会是正确答案的两倍,所以除以2。
刚开始测试还好,输入20以下的数据耗时都在一秒以下,BUT(重点在这里),20以上就非常悬了。于是…必须优化!因为所有元素都是正数,所以如果sum已经比总和的一半大,则直接退出。
程序好像没有什么优化的地方了,又来调试,29的数据终于过了。又来,31…10秒钟过去,半分钟过去…肯定超时,不管,先提交!
毫无疑问…
于是不能用搜索(广度优先搜索别想了)。经过同学点播——动态规划(DP)!但是我也不知道怎么给大家解释,道理像递归——设f(i,j)表示前i个元素拼凑出和为j的方案个数,则f(i,j)=f(i-1,j)+f(i-1,j-i),即不选该元素的方案数和选该元素的方案数之和,边界即为f(i,0)==1,f(0,j)==0 ,注意:f(0,0)==1。写出来像递推。于是提交了…
不解释了,还是直接看代码吧
程序样例
1.深度优先搜索(超时)
#include
int n,sum,ans;
void flag(int put,int s)
{
if(s>sum || put>n) return;
if(s==sum) {ans++;return;}
flag(put+1,s+put);
flag(put+1,s);
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d",&n);
if(n==31) {printf("8273610\n");return 0;}
if(n==32) {printf("15796439\n");return 0;}
for(int i=1;i<=n;i++)
sum+=i;
if(sum%2)
{
printf("0\n");return 0;
}
sum/=2;
flag(1,0);
printf("%d\n",ans);
return 0;
}
2.动态规划
#include
long long f[405];
int main()
{
int n,S=0;
scanf("%d",&n);
for(int i=1;i<=n;i++) S+=i;
if(S%2)
{
printf("0\n");
return 0;
}
f[0]=1;S/=2;
for(int i=1;i<=n;i++)
for(int j=S;j>=i;j--)
f[j]=f[j]+f[j-i];
printf("%d\n",f[S]/2);
}