經典揹包問題

拍壇對酒發表於2020-11-02

01揹包問題

題目:洛谷P1048
AC程式碼:

#include<stdio.h>
int t[110],v[110];
int dp[1010];
int max(int a,int b)
{return a>b?a:b;}
int main()
{
	int T,M;
	scanf("%d%d",&T,&M);
	int i,j;
	for(i=1;i<=M;i++)
	  scanf("%d%d",&t[i],&v[i]);
	for(i=1;i<=M;i++)
	  for(j=T;j>=t[i];j--)
	    dp[j]=max(dp[j],dp[j-t[i]]+v[i]);
	printf("%d",dp[T]);
	return 0;
}

01揹包問題思路分析
第i重迴圈中的dp[j]表示使用前i個物品,在j空間下能獲得的最大價值。
對於第i個物品,只有 用 或 不用 兩個選擇,應選擇能使價值最大的一個。
如果不用,j空間的揹包還是能裝之前i-1個物品中最大的那個值;如果用,j空間的揹包中最大值為第i個物品的值加上j-t[i]空間中能裝的最大值。

完全揹包

題目:洛谷P1616
AC程式碼:

#include<stdio.h>
#define ll long long
ll t[10010],v[10010];
ll dp[10000010];
ll max(ll a,ll b)
{return a>b?a:b;}
int main()
{
	ll T,M;
	scanf("%lld%lld",&T,&M);
	int i,j;
	for(i=1;i<=M;i++)
	  scanf("%lld%lld",&t[i],&v[i]);
	for(i=1;i<=M;i++)
	  for(j=t[i];j<=T;j++)
	    dp[j]=max(dp[j],dp[j-t[i]]+v[i]);
	printf("%lld",dp[T]);
	return 0;
}

完全揹包就是在01揹包的基礎上把內迴圈的倒序改成了正序
倒序巧妙地使每一個物品只能用一次
比如i=1時,即只用第一個物品時,從T到t[i],都是建立在之前揹包為0的基礎上,即這個物品只能用一次。
而正序時,從t[i]到T,j=t[i]時可以放一個物品,j=2*t[i]時,之前的dp[j-t[i]]已經有一個物品了,正序時是在之前已經放過這個物品的基礎上繼續看放還是不放。所以倒序改正序即可。

多重揹包

經典的二進位制轉換法
題目:洛谷P1776
AC程式碼:

#include<stdio.h>
int v[1000005],w[1000005];//v為價值,w為重量
int dp[1000005];
int max(int a,int b)
{return a>b?a:b;} 
int main()
{
	int n,W;
	scanf("%d%d",&n,&W);//n為寶物種數,W為最大載重 
	int i,j,cnt=0;
	int a,b,c; 
	for(i=1;i<=n;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		for(j=1;j<=c;j*=2)
		{
			v[++cnt]=j*a;
			w[cnt]=j*b;
			c-=j;
		}
		if(c!=0)
		{
			v[++cnt]=c*a;
			w[cnt]=c*b;
		}
	}
	for(i=1;i<=cnt;i++)
	  for(j=W;j>=w[i];j--)
	    dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
	printf("%d",dp[W]);
	return 0;
}

多重揹包思路就是轉化為01揹包。因為數量是固定的,我們很容易想到的思路就是把數量為n的一種物品轉化為n個物品來做01判斷。但是這樣數量太大,會超時。
一種特殊的辦法是二進位制轉化,也就是對數量為n的物品,把它轉化成1+2+22+…+2k+剩下的。這樣對1~n的每一個數,都能由上面的某些陣列合出來。
即把n個物品打包成若干包,每一包數量為上面的數列,每一包算一個物品。這樣,不管想選擇幾個物品放入,都能等價地轉化為選擇某些包組合出這個數來。
這樣就把多重揹包轉化為了對這些小包的01揹包問題。

相關文章