天天看點

動态規劃-背包問題背景介紹

動态規劃之背包

  • 背景介紹
    • 0-1背包問題
    • 完全背包問題
    • 多重背包
    • 多重背包二進制優化
    • 總結

背景介紹

動态規劃與貪心算法有相同之處,他們通常都是用來求解最優化的問題,但是貪心每次所得的都是局部最優,最終這個局部最優就是最終的答案,例如最小化找零問題,我們每次嘗試使用最大面值。相比與動規劃的問題,動态規劃問題通常具有這樣幾個特性。

1、具有子問題結構

2、從一個狀态轉移到另一個狀态

3、邊界條件

通常我們求解動态規劃都是根據這樣一些性質進行求解。下面主要講的是動态規劃裡面比較經典的背包問題。

0-1背包問題

問題描述

有 N 件物品和一個容量是 V 的背包。每件物品隻能使用一次。第 i 件物品的體積是 vi,價值是 wi。求解将哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大。輸出最大價值。

輸入格式

第一行兩個整數,N,V,用空格隔開,分别表示物品數量和背包容積。接下來有 N 行,每行兩個整數 vi,wi,用空格隔開,分别表示第 i 件物品的體積和價值。

輸出格式

輸出一個整數,表示最大價值。

資料範圍

0<N,V≤1000

0<vi,wi≤1000

輸入樣例

4 5

1 2

2 4

3 4

4 5

輸出樣例

8

求解思路

對于動态規劃,首先可以思考如何描述狀态,假設我們用dp[i][j]描述在背包剩餘體積為j的情況下,選用前i件物品的最大價值。然後我們對于每件物品有兩種選擇選或者不選。不選擇第i件物品的情況下,dp[i][j] = dp[i-1][j],選擇第i件物品的時候,dp[i][j] = dp[i][j-v[i]]+w[i] (j>=v[i])。邊界條件可以直接全部初始化為0。

代碼實作

#include <iostream>
#include <cstring>
using namespace std;
/*
  1、dp[i][j] = dp[i-1][j] 第i件物品不拿 
 2、dp[i][j] = max(dp[i][j],dp[i-1][j-v[i]]+w[i])  (j>=v[i])	
*/
#define N 1010
int dp[N][N];//dp數組
int v[N],w[N];//體積,和價值
int n,m; 
int main(){
	cin>>n>>m;//物品數量 體積 
	for(int i = 1;i<=n;i++)
	cin>>v[i]>>w[i];
	
	for(int i = 1;i<=n;i++){
		for(int j = 1;j<=m;j++){
			dp[i][j] = dp[i-1][j];
			if(j>=v[i]){
				dp[i][j] = max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
			}
		}
	}
	int res = 0;
	for(int i = 1;i<=m;i++){
		res = max(res,dp[n][i]);
	}
	cout<<res<<endl; 
} 
           

優化

對于上述的二維數組表示,我們可以通過一維數組對其進行等價替換。我們在枚舉體積的時候從大到小枚舉,這樣就可以保證 dp[j] = max(dp[j],dp[j-v[i]]+w[i]); (j>=v[i],j從m開始)。

代碼實作

#include <iostream>
#include <cstring>
using namespace std;
#define N 1010
int dp[N];//dp數組
int v[N],w[N];//體積,和價值
int n,m; 
int main(){

	cin>>n>>m;//物品數量 體積 
	for(int i = 1;i<=n;i++)
	cin>>v[i]>>w[i];
	for(int i = 1;i<=n;i++){
		for(int j = m;j>=v[i];j--){
			dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
		}
	}
	cout<<dp[m]<<endl; 
}
           

完全背包問題

完全背包問題與0-1背包問題的差別就在于物品可以拿的次數,如果是0-1背包,每件物品隻能拿一次,完全背包在不超過背包容量的情況下,無限次。這是兩者的根本差別。

問題描述

有 N 種物品和一個容量是 V 的背包,每種物品都有無限件可用。第 i 種物品的體積是 vi,價值是 wi。求解将哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大。輸出最大價值。

輸入輸出與0-1背包一緻。

代碼實作

#include <iostream>
#include <cstring>
#include <algorithm>

/**
完全背包問題:
m:物品數  n:背包容量 
dp[i] 容量為i的背包所裝的物品的最大價值 
int res = max{dp[0]....dp[n]} 
for(int i = 0;i<m;i++)
 for(int j = n;j>=v[i];j--)
   for(int k = 0;k*v[i]<=j;k++)
    dp[j] = max(dp[j],dp[j-k*v[i]]+w[i]*k);
    
 for(int i = 0;i<m;i++)
 for(int j = v[i];j<=n;j--)
    dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
    
*/ 
using namespace std;
const int N = 1010;
int dp[N]; 
int m,n;
int main(){
	cin>>m>>n; //物品數 容量 
 
	for(int i=0;i<m;i++)
	{
		int v,w;
		cin>>v>>w;
		for(int j = v;j<=n;j++){
			dp[j] = max(dp[j],dp[j-v]+w);
		}
	}
	  	
	cout<<dp[n]<<endl;
}
           

多重背包

問題描述

有 N 種物品和一個容量是 V 的背包。第 i 種物品最多有 si 件,每件體積是 vi,價值是 wi。求解将哪些物品裝入背包,可使物品體積總和不超過背包容量,且價值總和最大。輸出最大價值。

輸入格式

第一行兩個整數,N,V,用空格隔開,分别表示物品種數和背包容積。接下來有 N 行,每行三個整數 vi,wi,si,用空格隔開,分别表示第 i 種物品的體積、價值和數量。

輸出格式

輸出一個整數,表示最大價值。

資料範圍

0<N,V≤100

0<vi,wi,si≤100

輸入樣例

4 5

1 2 3

2 4 1

3 4 3

4 5 2

輸出樣例

10

求解思路

多重背包可以拆成多個0-1背包,對于每一個物品在沒有達到背包容量之前我們都可以有0-到s中選擇,也就是也可選擇0件,1件,2件。。。s件。可以0-1背包的基礎上加上一層for循環枚舉一下物品數。

代碼實作

#include <iostream>
#include <algorithm>
#include <cstring>
/**
  n重背包問題 
  選的個數有限制,未優化的多重背包
  
  狀态
  dp[i] 體積為i的情況下,最大價值
  max{dp[0]....dp[n]}
  for(int i = 0;i<m;i++)
   for(int j = n;j>=v[i];j--)
    for(int k = 0;k*v[i]<=n;k++){
          dp[j] = max(dp[j],dp[j-k*v[i]]+k*w[i])	
	}
*/
using namespace std;
const int N = 110;
int v[N],w[N],s[N],dp[N];
int main(){
	int m,n;
	cin>>m>>n;
	for(int i = 1;i<=m;i++){
		cin>>v[i]>>w[i]>>s[i];
	}
 for(int i = 1;i<=m;i++){
 	   for(int j = n;j>=0;j--){
   	    for(int k = 1;k*v[i]<=j&&k<=s[i];k++){
          dp[j] = max(dp[j],dp[j-k*v[i]]+k*w[i]);
	}
   }
 }
	cout<<dp[n]<<endl;
	return 0;
} 
           

多重背包二進制優化

對于上述多重背包問題求解過程中使用了三重for循環,對于事件複雜度要求高的化,我們需要進行優化。優化的過程主要是拆分,假設物品體積為7,對于0-7我們可以使用1 2 4三個數表示出來。

代碼實作

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
/**
多重背包問題二進制優化,
多重背包轉化成0-1背包 
*/
const int N = 2010;
int dp[N];
int m,n;
struct Bag{
	int v;
	int w;
};
int main(){
	vector<Bag> bags;
	cin>>m>>n;
	for(int i = 0;i<m;i++)
	{
		int v,w,s;
		cin>>v>>w>>s;
		for(int k = 1;k<=s;k*=2){
			s -= k;
			bags.push_back({v*k,w*k});
		}
		if(s>0){
			bags.push_back({s*v,s*w});
		}
	}
	for(int i = 0;i<bags.size();i++){
	  for(int j = n;j>=bags[i].v;j--){
	  	dp[j] = max(dp[j],dp[j-bags[i].v]+bags[i].w);
	  } 
	} 
	cout<<dp[n]<<endl;
	return 0;
} 
           

總結

對于背包問題的求解,0-1背包問題是基礎。在0-1背包的問題上進行擴充。确定好狀态轉移方程。以及邊界條件。

繼續閱讀