Description
給定一個正整數的集合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 10
這道題是本校OJ上的一道題,作為算法設計與分析課程的作業,要求使用動態規划,我最開始的想法是將這個問題往背包問題上轉換,思路應該是正確的,不過一開始沒能實現,最終還是寫出來了,但這里要介紹一種老師講的方法。
用數組a[i]來存儲正整數的集合。
構造一個二維數組表t[i][j],表示{a1,a2......ai}存在子集和為j,並將其值賦為真。
這句話該怎么理解呢?
比如此時ai={3,4,8,1,10}
a1=3,a2=4,a3=8,a4=1,a5=10
那么{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]=1
看到這里是不是就應該明白這個二維數組的含義了吧,我們只要求得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,至此子集中所有的元素都找到了。為1、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; }