Descriptionphp
给定一个正整数的集合A={a1,a2,….,an},是否能够将其分割成两个子集合,使两个子集合的数加起来的和相等。例A = { 1, 3, 8, 4, 10} 能够分割:{1, 8, 4} 及 {3, 10},
Input
第一行集合元素个数n n <=300 第二行n个整数
Output
若是能划分红两个集合,输出任意一个子集,不然输出“no”
Sample Input
5
1 3 8 4 10
Sample Output
3 10ios
这道题是本校OJ上的一道题,做为算法设计与分析课程的做业,要求使用动态规划,我最开始的想法是将这个问题往背包问题上转换,思路应该是正确的,不过一开始没能实现,最终仍是写出来了,但这里要介绍一种老师讲的方法。算法
用数组a[i]来存储正整数的集合。数组
构造一个二维数组表t[i][j],表示{a1,a2......ai}存在子集和为j,并将其值赋为真。spa
这句话该怎么理解呢?设计
好比此时ai={3,4,8,1,10}code
a1=3,a2=4,a3=8,a4=1,a5=10blog
那么{a1,a2,a3}构成的子集有{∅}、{a1}、{a2}、{a3}、{a1,a2}、{a2,a3}、{a1,a3}、{a1,a2,a3},对应的子集和为0,3,4,8,7,12,11,15。递归
这样就能够获得t[3][0]=1 t[3][3]=1 t[3][4]=1 t[3][8]=1 t[3][7]=1 t[3][12]=1 t[3][11]=1 t[3][15]=1ip
看到这里是否是就应该明白这个二维数组的含义了吧,咱们只要求得t[n][sum/2]=1,说明{a1,a2......an}存在一组子集正数其和为sum/2,(这里的sum表示整个整数集合之和),也就是说存在两个子集合加起来的和相等。
既然是动态规划咱们须要探究一下其递归方程应该如何表示
若是t[i-1][j]=1 那么必定有t[i][j]=1 由于前i-1个正整数的子集可以组成的和为j,那么再加上第i个正整数,仍是用以前的子集,可以组成和仍是j。
若是t[i-1][j-a[i]]=1 那么也会有t[i][j]=1 由于前i-1个正整数的子集可以组成的和为j-a[i],那么再加上第i个正整数,仍是用以前的子集再加上a[i],可以组成和j。
由此咱们获得了递归迭代方程:
t[1][0] =1
t[1][a[1]] =1
t[i][j] =1 《==( t[i-1][j]=1 || t[i-1][j-a[i]]=1 )
仍是以上面的ai={3,4,8,1,10}为例子,写一下t[i][j]的二维数组表
咱们根据t[n][sum/2]的值就能判断是否存在两个相等的子集和,但怎么样肯定子集呢?
肯定子集的方法有不少,OJ后台只须要一种答案便可,这里讲一下我使用的方法。
个人想法就是观察上面的表看看sum/2是有那些元素贡献的,其实上面的表是从左上角到右下角打印的,咱们从后向前逆推组成元素时就须要从右下角向左上角推理,发现t[4][13]是13列第一个带有真值的,最终结果t[5][13]也为真,说明a[4]确定是归入子集中,sum/2-a[4]后,也就是13-1=12,咱们再来看t[3][12]是12列第一个带有真值的,说明a[3]确定被归入子集中,12-a[3],也就是12-8=4;咱们再来看t[2][4]是第4列第一个带有真值的,说明a[2]确定被归入子集中,4-a[2],也就是4-4=0,至此子集中全部的元素都找到了。为一、8 、4
以前有同窗不明白第一张表中的数据是怎么来的,这里我再画一一张表,追踪一下动态规划的过程。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int main() { int n; int sum=0; int a[310]; int ans[155]; scanf("%d",&n); for(int i=1; i<=n; i++) { scanf("%d",&a[i]); sum+=a[i]; } if(sum%2!=0) { printf("no\n"); return 0; } int m=sum/2; int dp[n+1][m+1]; memset(dp,0,sizeof(dp)); dp[1][0]=1; dp[1][a[1]]=1; for(int i=2; i<=n; i++) //前i个元素 { for(int j=0; j<=m; j++) //能够构成j { if(dp[i-1][j]||dp[i-1][j-a[i]]) { dp[i][j]=1; } } } if(!dp[n][m]) { printf("no\n"); return 0; } int cnt=0; for(int j=m; j>=0; j--) { for(int i=n; i>=1; i--) { if(dp[i][j]&&!dp[i-1][j])//二维数组中每一列最顶部的那个T { ans[cnt]=a[i]; cnt++; j=j-a[i]; if(j==0)//找完结束 { break; } } } } for(int i=0; i<cnt-1; i++) { printf("%d ",ans[i]); } printf("%d\n",ans[cnt-1]); return 0; }