1. 程式人生 > >動態規劃DP-揹包問題-ACM

動態規劃DP-揹包問題-ACM

AC code:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 125;
int dp[maxn+5][maxn + 5];
int n = 0;
void solve(){
	fill(dp[0],dp[0]+(maxn+5)*(maxn+5),0);//詳情見揹包九講的初始化問題 
	for(int i = 0;i <= maxn;++i){//初始化,這是合法狀態 
		dp[1][i] = 1;
	}
	for(int i = 1;i <= maxn;++i){//恰好裝滿 
		dp[i][0] = 1;
	}
	for(int i = 2;i <= maxn;++i){
		for(int j = 1;j <= maxn;++j){
			dp[i][j] = dp[i-1][j] + (j >= i ? dp[i][j-i]:0);
		}
	}
}
int main(){
	solve();
	while(~scanf("%d",&n)){
		printf("%d\n",dp[n][n]);
	}
	return 0;
}
AC code:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 300;
int dp[18][maxn + 5];//dp[i][j]表示使用前i種貨幣,湊出j元的方法數 
int n = 0;
void solve(){
	fill(dp[0],dp[0]+18*(maxn+5),0);
	for(int i = 0;i <= maxn;++i){
		dp[1][i] = 1;
	}
	for(int i = 1;i <= 17;++i){//要初始化為1 
		dp[i][0] = 1;
	}
	for(int i = 2;i <= 17;++i){
		for(int j = 1;j <= maxn;++j){
			dp[i][j] = dp[i-1][j] + (j >= i*i? dp[i][j-i*i]:0);//i,j狀態由只是用前i-1種貨幣 
			//湊成j元的方法數+使用前i種貨幣j-i元(在加上i元貨幣)的方法數 
		}
	}
	/*
	for(int i = 1;i <= 4;++i){
		for(int j = 0;j <= 4;++j){
			printf("%d%c",dp[i][j],(j == 10)?'\n':' ');
		}
	}
	*/
}
int main(){
	solve();
	while(~scanf("%d",&n) && n != 0){
		printf("%d\n",dp[17][n]);
	}
	return 0;
}

上面兩個問題的初始化是一樣的。揹包九講有段話話很重要
初始化的細節問題:
我們看到的求解最優解的揹包問題中,事實和桑有兩種不太相同的問法。

  1. 要求”揹包恰好裝滿“ 時的最優解
  2. 不要求揹包一定要被裝滿時的最優解

我們上面所討論的就是第2種, 不要求揹包一定要被裝滿時的最優解。
一種區別這兩種問法的實現方法是在初始化的時候有所不不同。

如果是第一種問法,要求恰好裝滿揹包,那麼在初始化時除了 dp[0]dp[0] 為0, 其他dp[1…W]均設為−∞dp[1…W]均設為−∞ ,這樣就可以保證最終得到 dp[W]dp[W] 是一種恰好裝滿揹包的最優解
如果並沒有要求必須把揹包裝滿,而是隻希望價格儘量大,初始化時應該將dp[0…W]dp[0…W] 全部設為0。

這是為什麼呢?可以這樣理解:初始化的dpdp 陣列事實上就是在沒有任何物品可以放入揹包時的合法狀態。如果要求揹包恰好裝滿,那麼此時只有容量為0的揹包可以在什麼也不裝的狀態下被 “恰好裝滿” ,此時揹包價值為0。其他容量的揹包均沒有合法的解,屬於未定義的狀態,所以都應該被賦值為 −∞−∞ 。當前的合法解,一定是從之前的合法狀態推得的

如果揹包並非必須被裝滿,那麼任何容量的揹包都有一個合法解 “什麼也不裝”,這個解的價值為0,所以初始化時狀態的值也就全部為0了。
接下來還是一個多重揹包的問題:https://vjudge.net/contest/263202#problem/C

AC code:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 8e3 ;
int num[4];
int val[4] = {0,1,2,5};
bool dp[maxn+10];
void solve(){
	int ma = num[1] + 2*num[2] + 5*num[3];
	fill(dp,dp+maxn+10,false);
	dp[0] = true;
	for(int i = 1;i <= 3;++i){
		for(int k = 1;k <= num[i];++k){
			for(int j = ma;j >= 1;--j){
				dp[j] = (dp[j] || dp[j-val[i]]);
				//cout << i << " " << j << " " << dp[j] << endl;
			}
		}
	}
	for(int i = 1;i <= ma+1;++i){
		if(dp[i] == false){
			printf("%d\n",i);
			return ;
		}
	}
}
int main(){
	while(~scanf("%d %d %d",&num[1],&num[2],&num[3])){
		if(num[1] == 0 && num[2] == 0 && num[3] == 0){
			break;
		}
		solve();
	}
	return 0;
}
AC code:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100 + 10;
int dp[maxn][maxn][maxn];//dp[i][j][k]表示使用了i種水果,有了j個盤,在第i種水果使用了k個 
struct node{
	int mi,ma;
};
node num[maxn];
#define INF 1e6
int n = 0,m = 0;
void solve(){
	fill(dp[0][0],dp[0][0]+maxn*maxn*maxn,0);
	for(int i = 1;i <= m;++i){//非法狀態初始化為-1 
		dp[0][i][0] = -INF;
	}
	
	for(int i = num[1].mi;i <= num[1].ma;++i){//第一個盤子的初始化 
		dp[1][i][i] = 1;
	}
	for(int i = 2;i<= n;++i){
		for(int j = 0;j <= m;++j){
			for(int k = num[i].mi;j +k <= m && k <= num[i].ma;++k){
				for(int l = num[i-1].mi;l <= num[i-1].ma;++l){
					if(dp[i-1][j][l] == -INF){
						continue;
					}
					dp[i][j+k][k] += dp[i-1][j][l];//由第i-1種轉移而來,所以要遍歷i-1種的情況 
				}
			
			}
		}
	}
	int ans = 0;
	for(int i = num[n].mi;i <= num[n].ma;++i){//第n種水果使用了i個的種數 
		ans += dp[n][m][i];//答案要把他們加起來 
	}
	printf("%d\n",ans);
}
int main(){
	while(~scanf("%d %d",&n,&m)){
		for(int i = 1;i <= n;++i){
			scanf("%d %d",&num[i].mi,&num[i].ma);
		}
		solve();
	}
	return 0;
}