您的位置:首页 > 大数据 > 人工智能

竞赛题目讲解 - 【USACO TRAINING】子集的和

2017-06-11 22:47 363 查看

【USACO TRAINING】子集的和

题目描述

对于从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<set>
(注: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秒钟过去,半分钟过去…肯定超时,不管,先提交!

Time Limit Exceeded

毫无疑问…

于是不能用搜索(广度优先搜索别想了)。经过同学点播——动态规划(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。写出来像递推。于是提交了…

Accepted

不解释了,还是直接看代码吧

程序样例

1.深度优先搜索(超时)

#include<cstdio>
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<cstdio>
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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: