集合划分!!!

Description
对于从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。
Input

有多组测试数据。

对于每组测试数据,输入一个整数n。

Output
对于每组测试数据,输出划分方案总数,如果不存在则输出0。

对于这个题目,我实在是无言以对,实在是太变态了,我以为,对大的数字就是39,可以进行暴力的,但是事实告诉我,最后一个

数字是暴力不出来的,本来我想如果能暴力出来就可以使用打表的方法,就可以轻松解决了,可惜啊,当我暴力最后一个数字的时候我等了20多分钟,结果是个-1,所以我改变一下范围,发现他就出不来了,后来我发现我的方法实在是太浪费时间和空间了,后来我听同学的方法使用dp,其实我dp不行的,如果我会dp的话,早就不用这样的方法了,后来他给我写了伪代码,我想了一下,打完之后就过了,实在是太简单了,我都无语了。



解题思路 :

如果我输入一个数字N,代表着1~N 这些数字,而且我是求的是SUM/2 能有多少种方法(SUM 是1~N 的和),第一个注意的地方

如果SUM 是个基数,想都不要想,直接给他pass 掉,因为奇数不可能分成相等的两半的,所以我们对于相加的结果是奇数的直接

给出答案0,剩下的就是相加和为偶数的了,我们就要想这样的一件事情,我们要求出SUM/2 有多少种方法,所以我们的方法总数中

一定会有重复的,最后的方法总数要除以2 的,为什么要除以2 呢,举个例子,如果N = 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}

 

这是四种方法把7的集合分成两个部分但是你在计算的时候只要计算一下,7这个集合中有多少个子集能等于SUM/2

的,可能你不知道其实有某些集合是一组的不用计算,也就是说,他把所有的子集等于SUM/2 的都计算了一边,但是你要的是不是所有的集合而是有多少组这样的集合,最后就直接除以2,就可以了。


接下来如何进行计算呢 ?

其实这样的一个思想是可以解决很多的题目的,我是这样想的,如果这个集合的数目是7,那他的把集合中所有的元素都加起来最多也就得到SUM = (7*8/2) ;SUM = 28,我想这个递增关系式还是能看懂的吧,这个很简单的,

7这个集合里面的元素分别是 1~7,而且都是递增的,所以初中还是高中,我给忘了,有一个递增的公式,

SUM = N*(N+1)/2 ; 这样就能求出这个集合所有元素相加的和,而且这个元素最大的相加和就是这个数字了 ,没有比这个数字还大的了,我们可以这样想:


我使用两个for循环,这两个for循环就是来进行计算的,第一的for循环用来 1~N 进行遍历,第二个for循环让它从

SUM 到 1进行遍历,在第二个for循环里进行两次if判断就可以解决了,



for(i = 1;i <= n;i++){

for(j = sum;j >= 1;sum--){

if(dp[i]) dp[j+i] += dp[j];//这里是进行统计构成这个数字有多少种方法

if(i == j) dp[j]++; //由于本身也可以算是都成这个数字的方法,所以本身也要加一

}

}




可能有时候还是不明白这个是什么意思,我在解释一下,为了方便以后的学习,

如果有一个数字,dp[n] = 2,就是说明能构成他这样的数字的子集有2种方法,

如果dp[n+i] = 2 ;这就说明了构成n+i这个数字的集合有2种,为什么会这样的呢?(前提n+i没有超过SUM)

dp[n] 构成的方法是2,你在每一个构成的方法后面加上一个元素i,是不是就是n+i的构成方法了。


还要注意一下的就是,在进行计算的时候一定要注意dp数组的清零问题,如果不进行这一步,我们计算的结果会有问题,最后我们只要输出dp[SUM/2]/2 就可以了。



扩展一下,如果一个数组里面的元素不是递增的呢,是自己出入的数字,而且要求出指定数字的构造方法有多少种


其实这样的题目和上面的方法是一样的,你可以把输入的元素放入一个数组中,计算出这个数组中最大的数字是多少,这样就可以在第二次的for循环中进行计算了,第一次的for循环就0~n-1 数组的遍历,


for(i = 0;i < n;i++){

for(j = sum;j >= 0;j++){

if(dp[j]) dp[j+arr[i]] += dp[j];

if(j == arr[i]) dp[j]++;

}

}










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值