動態規劃之多重部分和問題
阿新 • • 發佈:2019-01-27
分析:拿到這道題目,當然可以用遞迴的形式進行深度搜索遍歷每種情況,但是效率會非常低。一般遞迴帶有前後數值關係的遞迴是可以用動態規劃轉化的,效率會提高很多。
至於動態規劃,最重要的就是準確定義動態陣列的意義並找準前後的遞推式.我們這裡定義dp[i][j]為bool型別,其意義表示為用前i種數(下標為0~i-1)能否湊成和j,能的話則dp[i][j]=true,反之則dp[i][j]=false,那麼根據其前一種狀態dp[i-1][j-k*a[i-1]](0<=k<=m[i-1]),如果dp[i-1][j-k*a[i-1]]=true,那麼對於第i個數a[i-1]我們只要再用k個,那麼就可以用前i個數湊成和為j。這是很容易理解的。遞推關係式為:
具體的程式碼如下:
#include<iostream> using namespace std; #define Max_N 100 #define Max_K 100000 bool dp[Max_N+1][Max_K+1]; int a[Max_N]; int m[Max_N]; int n,K; void solve() { dp[0][0]=true; for(int j=1;j<=K;j++) dp[0][j]=false; //用前0種數湊成和不為0的,肯定不行 //dp[i][j] 用前i種數(下標0~i-1)能不能湊成和為j for(int i=1;i<=n;i++) { for(int j=0;j<=K;j++) { //下標為i-1的數使用k次 for(int k=0;k<=m[i-1] && k*a[i-1]<=j;k++) { //不斷或等 dp[i][j] |= dp[i-1][j-k*a[i-1]]; } } } if(dp[n][K]) cout<<"Yes\n"; else cout<<"No\n"; return ; } int main() { cin>>n>>K; for(int i=0;i<n;i++) { cin>>a[i]; } for(int i=0;i<n;i++) { cin>>m[i]; } solve(); }
其實大家也能看到這個程式的缺點就在於三重迴圈下,效率還是比較低,那麼我們就想著有沒有更高效的方法,那麼我們就可以使用另一種定義方法dp[i][j]表示:用前i種數(下標0~i-1)在能湊成和為j時,第i個數(下標為i-1)最多可以剩餘多少個,-1表示前i種數湊不成和為j,遞推關係式如下(每段的意義我將會寫在程式的註釋中):
具體的程式碼和遞推式的意義如下:
#include<iostream> using namespace std; #define Max_N 100 #define Max_K 100000 int dp[Max_N+1][Max_K+1]; int a[Max_N]; int m[Max_N]; int n,K; void solve() { dp[0][0]=0; for(int j=1;j<=K;j++) dp[0][j]=-1; //用前0種數湊成和不為0的,肯定不行 //dp[i][j] 用前i種數(下標0~i-1)在能湊成和為j時,第i個數(下標為i-1) //最多可以剩餘多少個,-1表示前i種數湊不成和為j for(int i=1;i<=n;i++) { for(int j=0;j<=K;j++) { if(dp[i-1][j]>=0) //如果前i-1種數就能湊成和為j,那麼第i個數a[i-1]可以一個都不用,全部剩下來 dp[i][j]=m[i-1]; else //前i-1種數湊不成和為j,那麼就用第i個數a[i-1]嘗試湊 { //如果要湊的和j小於a[i-1],那麼肯定湊不成j ------ j<a[i-1] //無法用前i個數湊不成和j-a[i-1],那麼無論再加不加上一個數a[i-1],都湊不成和j -----dp[i][j-a[i-1]<0 //如果在用前i個數能湊成和j-a[i-1],但是這時候a[i-1]數用完了,那麼也是湊不成的 ------dp[i][j-a[i-1]=0 if(j<a[i-1] || dp[i][j-a[i-1]]<=0) dp[i][j]=-1; else //在前i種數能湊成和j-a[i-1]並且數a[i-1]還有剩餘,那麼是能湊成的,再用掉一個a[i-1],所以個數減一 dp[i][j]=dp[i][j-a[i-1]]-1; } //cout<<"dp["<<i<<"]["<<j<<"]:"<<dp[i][j]<<endl; //測試使用 } } //dp[n][K]>=0表明前n個數(a[0]~a[n-1])在湊成和為K時,數a[i-1]不剩餘或者還有剩餘, //但是不管剩餘還是不剩餘,都是能湊成和為j if(dp[n][K]>=0) cout<<"Yes\n"; else cout<<"No\n"; return ; } int main() { cin>>n>>K; for(int i=0;i<n;i++) { cin>>a[i]; } for(int i=0;i<n;i++) { cin>>m[i]; } solve(); }
PS:至於動態規劃的題目,重點在於定義好動態陣列的意義和找準遞推式,這個沒辦法,這個的話就只能多做題多感悟了