1. 程式人生 > 其它 >揹包問題(二)——完全揹包問題

揹包問題(二)——完全揹包問題

之前我們已經介紹了0/1揹包問題,現在我們以洛谷P1616為例,介紹一下完全揹包問題

完全揹包問題就是將0/1揹包問題中的每樣物品只能拿一次這個限制條件去掉,每樣物品可以無限次裝入。

對於完全揹包的圖形解釋,我擷取《LeetCode_101》內的解釋展現出來:

簡要說一下推導過程:因為我們可以多次拿取物品,在總容積不超過j的情況下,我們也最多隻能裝j/v[i]=k個物品,那麼狀態轉移方程就寫為

dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i],dp[i-1][j-2*v[i]]+2w[i],...dp[i-1][j-k*v[i]])。

利用0/1揹包思想,我們可以得到

dp[i][j-v]=max( dp[i-1][j-v] , dp[i-1][j-2v]+w[i],dp[i-1][j-3v]+2w[i], dp[i-1][j-4v]+3w[i],..., dp[i-1][j-kv]+(k-1)w[i])。兩邊同時加上w[i]後可以替換上式中的max(dp[i-1][j-v[i]]+w[i],dp[i-1][j-2*v[i]]+2w[i],...dp[i-1][j-k*v[i]]).

所以我們就得到了狀態轉移方程——dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i])

同樣的,我們可以對上述dp陣列進行狀態壓縮,將第一個維度去掉。思路與0/1揹包問題基本一致。

壓縮後的狀態轉移方程:

dp[j]=max(dp[j],dp[j-w[i]]+w[i])

程式碼可寫為:

for(int i=0;i<N;i++)
    for(int j=w[i];j<W;j++)
        dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

注意此時我們這裡與0/1揹包問題不同,此時我們是正向遍歷陣列。因為我們需要利用到j-w[i]列的資訊。注意此時右邊的dp[j]是第i-1行的值。

因此,介紹完完全揹包問題後,洛谷的這道程式設計題就十分容易了:

#include<iostream>
#include<vector>
#include<math.h>
using namespace std;

int main() {
	int t, m;
	cin >> t >> m;

	vector<long long> dp(t + 1, 0); //資料較大,有可能爆int,所以開long long。
	long long* times = new long long[m];
	long long* values = new long long[m];
	int tim, val;
	for (int i = 0; i < m; i++) {
		cin >> tim >> val;
		times[i] = tim;
		values[i] = val;
	}
	for (int i = 1; i <= m; i++) {
		for (int j = times[i-1]; j <= t; j++)
			dp[j] = max(dp[j], dp[j - times[i-1]] + values[i-1]);
	}

	cout << dp[t];
	delete[]times;
	delete[]values;
	return 0;
}