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; }