揹包問題

參考文獻:揹包九講php

一:01揹包問題

最基礎的揹包問題,關鍵是每一個物品只要一件,基本的狀態轉移方程就是:f[i][v]=max{f[i-1][v],f[i-1][v-w[i]]+v[i]}
有個須要注意的地方是:要求剛好裝滿揹包,那麼在初始化時除了f[0]爲0其它f[1..V]均設爲-∞,這樣就能夠保證最終獲得的f[N]是一種剛好裝滿揹包的最優解。若是並無要求必須把揹包裝滿,而是隻但願價格儘可能大,初始化時應該將f[0..V]所有設爲0。能夠這麼思考,由於要求裝滿,一開始只要f[0]時知足「裝滿」,這時爲0,其他的都沒有合法解,所以爲-∞。通常作法時間複雜度和空間複雜度都是O(N*V),能夠將空間複雜度降到O(V)

例子:HDU 2602:
空間複雜度爲:O(N*V):
16512821 2016-03-11 15:20:45 Accepted 2602 78MS 5732K 633 B G++ seasonal
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define max(a,b) a>b?a:b
int f[1050][1050];
int value[1050], volume[1050];

int main()
{
	int T;
	scanf("%d", &T);
	while (T--)
	{
		int n, v,i,j;
		memset(f, 0, sizeof(f));	
		scanf("%d%d", &n, &v);
		for (i = 1; i <= n; i++)
			scanf("%d", &value[i]);
		for (i = 1; i <= n; i++)
			scanf("%d", &volume[i]);
		for (i = 1; i <= n; i++)
			for (j = 0; j <= v; j++)
			{
				if (volume[i] <= j)//假如當前的能放入揹包才考慮狀態轉移方程
					f[i][j] = max(f[i - 1][j], f[i - 1][j - volume[i]] + value[i]);
				else			   //不然直接等於上一個,至關於這個不放
					f[i][j] = f[i - 1][j];
			}
		printf("%d\n", f[n][v]);
	}
	return 0;
}

空間複雜度O(V):
要點就是利用f[v]儲存上一個i-1的值,此時f[v]與f[v-w[i]]比較時其實已是在比較i-1時的值。注意要倒序,使j從V…0,由於此時f[j]存儲的實際是f[i-1][j]的值,若是從小到大算,假設要求f[5]先要求出f[3],求出f[3]後f[3]儲存的就是f[i][3],而計算f[5]須要的是f[i-1][3],因此要倒序保證小的值不會被更新。
16513374 2016-03-11 16:28:38 Accepted 2602 31MS 1424K 538 B G++ seasonal
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define max(a,b) a>b?a:b
int f[1050];
int value[1050], volume[1050];

int main()
{
	int T;
	scanf("%d", &T);
	while (T--)
	{
		int n, v,i,j;
		memset(f, 0, sizeof(f));	
		scanf("%d%d", &n, &v);
		for (i = 1; i <= n; i++)
			scanf("%d", &value[i]);
		for (i = 1; i <= n; i++)
			scanf("%d", &volume[i]);
		for (i = 1; i <= n; i++)
			for (j = v; j >= volume[i]; j--)//這裏是倒序,使f[j]與f[i-1][j],f[j-volume[i]]與f[i-1][j-volume[i]]相互對應
				f[j] = max(f[j], f[j - volume[i]] + value[i]);//f[j]至關於存了前一個i-1的f值,此時比較與本來二維一個道理
		printf("%d\n", f[v]);
	}
	return 0;
}


二:徹底揹包問題

與01揹包最大的不一樣在於物品的數量能夠是無窮個,因此基本的二維狀態轉移方程應該是這樣的:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]},0<=k*c[i]<=v
這裏用二維實際上是比較麻煩的,由於涉及到k很差寫,用一維的方法反而比較好寫:
for i=1..N
   for v=0..V
      f[v]=max{f[v],f[v-cost]+weight}
但這裏與01揹包不一樣之處在於它的v是升序的,01揹包中降序是爲了確保物品只加入一個,使上一個i-1的狀態(沒有加物品)不會被更新掉,而徹底揹包中由於物品有無窮個,反而須要不停更新,使上一個i-1的狀態也能夠是加過了物品,因此必需要用升序。

例子:POJ1384&&HDU1114 
題意:
往儲蓄罐裏存硬幣,已知空儲蓄罐和放滿後的重量,給出n種硬幣的價值和重量,要求裝滿儲蓄罐可能的最小价值和
要點:
首先是要求必須裝滿,這裏就是前面01揹包的初始化問題,DP[0]=0,其他的均賦值爲+∞,而後用徹底揹包求最小值。狀態轉移方程爲:dp[j] = min(dp[j], dp[j - w[i]] + p[i])
16536210 2016-03-13 10:29:57 Accepted 1114 78MS 1460K 697 B G++ seasonal
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define INF 0xfffffff
#define min(a,b) a>b?b:a
int dp[10005],w[505],p[505];

int main()
{
	int empty, full,T;
	int i, j;
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d%d", &empty, &full);
		int v = full - empty;
		int n;
		scanf("%d", &n);
		for (i = 0; i < n; i++)
			scanf("%d%d", &p[i], &w[i]);
		for (j = 0; j <= v; j++)
			dp[j] = INF;		//注意初始化
		dp[0] = 0;
		for (i = 0; i < n; i++)
			for (j = w[i]; j <= v; j++)//一維的徹底揹包
				dp[j] = min(dp[j], dp[j - w[i]] + p[i]);
		if (dp[v] == INF)
			printf("This is impossible.\n");
		else
			printf("The minimum amount of money in the piggy-bank is %d.\n", dp[v]);
	}
	return 0;
}

三:多重揹包

不一樣之處在於物品規定了個數,第i個物品有c[i]個。通常使用二進制拆解,將複雜度降爲O(V*Σlog n[i]),方法以下:
針對第i個物品有c[i]個,咱們能夠將c[i]拆成1,2,4,...,2^(k-1),n[i]-2^k+1,這樣想放1~n[i]中任意個均可以用前面的係數表示,如9=1+2+4+2,這樣同時將對應的物品價值和重量乘係數做爲一個新的物品,同時這個物品只有一個,就是01揹包。還有若是一開始物品數大於總重量/物品質量,就直接能夠視爲無窮個,由於反正都是用不光,就轉化爲徹底揹包問題。
僞代碼:
procedure MultiplePack(cost,weight,amount)
    if cost*amount>=V
        CompletePack(cost,weight)
        return
    integer k=1
    while k<num
        ZeroOnePack(k*cost,k*weight)
        amount=amount-k
        k=k*2
    ZeroOnePack(amount*cost,amount*weight)

例子:HDU2191
16570781 2016-03-16 14:37:38 Accepted 2191 0MS 1432K 985 B G++ seasonal
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define max(a,b) a>b?a:b
int v[1500], w[1500], c[1500],dp[1500];
int n, m;

void one_pack(int cost, int weight)//01揹包
{
		for (int j = m; j >= cost; j--)
			dp[j] = max(dp[j], dp[j - cost] + weight);
}
void complete_pack(int cost, int weight)//徹底揹包
{
	for (int j = cost; j <= m; j++)
		dp[j] = max(dp[j], dp[j -cost] + weight);
}
void multiple_pack(int cost, int weight, int count)
{
	int i;
	if (cost*count >= m)//若是一開始就count*cost>=m,能夠當作無限種也就是徹底揹包
	{
		complete_pack(cost, weight);
	}
	else
	{
		int k=1;
		while (k < count)
		{
			one_pack(k*cost, k*weight);//至關於01揹包
			count -= k;
			k *= 2;				//利用二進制思想能夠表示1-count的全部值
		}	
		one_pack(count*cost, count*weight);//最後處理剩下的個數
	}
}


int main()
{
	int t;
	scanf("%d", &t);
	while (t--)
	{
		memset(dp, 0, sizeof(dp));
		scanf("%d%d", &m, &n);
		for (int i = 0; i < n; i++)
			scanf("%d%d%d", &w[i], &v[i], &c[i]);
		for (int i = 0; i < n; i++)
			multiple_pack(w[i], v[i], c[i]);
		printf("%d\n", dp[m]);
	}
	return 0;
}
相關文章
相關標籤/搜索