揹包問題

這裏寫了做者學過的一些揹包問題的解法,但願能爲新入門DP的OIer提供便利。php

\(\LaTeX\) 懶得修了,湊合着看吧QwQ~c++

2019/11/25:更新了混合揹包。
2020/09/24:更新了多重揹包的二進制優化和加二進制優化後的混合揹包~數組

01揹包

01揹包解決的就是有一堆東西,有體積量和價值,要放到一個容量爲m的揹包裏,使得價值之和最大。
重點: 每一個物品只有1個。
之因此叫01揹包,由於沒個物品就是取或不取,取是1,不取是0。優化

思路:\(dp_{i,j}\)表示前i個物品放到容量爲j的揹包中的最大價值,則
\(dp_{i,j}=max(dp_{i-1,j},dp_{i-1,j-w_i}+c_i)\)
\(dp_{i-1,j}\)表示不取這個東西,那麼容量仍是\(j\)\(dp_{i-1,j-w_i}+c_i\)表示取,那麼以前的容量就是\(j-w_i\)spa

優化: 咱們發現,咱們只須要\(dp_{i-1}\),而不須要更前面的數據,因此能夠換成兩個數組的滾動數組,而後,咱們發現,只須要i以前的數保留便可,那麼能夠從後往前賦值,這樣只要一個數組就能完成。code

代碼:ci

#include<bits/stdc++.h>
using namespace std;
int n,m;
//n是物品數,m是揹包容量 
int w[505],c[505];
//wi表示第i個物品的重量,ci表示價值 
int dp[6005];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i]>>c[i];
	for(int i=1;i<=n;i++)//枚舉每一個物品 
	 for(int j=m;j>=w[i];j--)//枚舉揹包容量 
	 	dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
	 	//狀態轉移方程 
	cout<<dp[m];
	return 0;
}

徹底揹包:

徹底揹包,就是一個物品能取無限次,求最大價值。get

咱們寫01揹包時之因此要從後往前,是要避免重複取一個東西,而徹底揹包就是一個物品能取無限次,因此只要把內層循環改爲\(w_i\)~\(m\)便可。it

代碼:io

#include<bits/stdc++.h>
using namespace std;
int n,m;
//n是物品數,m是揹包容量 
int w[505],c[505];
//wi表示第i個物品的重量,ci表示價值 
int dp[6005];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i]>>c[i];
	for(int i=1;i<=n;i++)//枚舉每一個物品 
	 for(int j=w[i];j<=m;j++)//枚舉揹包容量 
	 	dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
	 	//狀態轉移方程 
	cout<<dp[m];
	return 0;
}

多重揹包

多重揹包,第i個物品能取\(0\)~\(s_i\)個,求最大價值。

咱們能夠把多重揹包的一個物品取屢次當作多個同樣的物品,例如,1號物品有2個,咱們能夠當作1和1。而後作01揹包便可。

代碼:

#include<bits/stdc++.h>
using namespace std;
int n,m;
//n是物品數,m是揹包容量 
int w[505],c[505],s[505];
//wi表示第i個物品的重量,ci表示價值,si表示數量 
int dp[6005];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>w[i]>>c[i]>>s[i];
	for(int k=1;k<=n;k++)//枚舉物品種類 
	 for(int i=1;i<=s[k];i++)
	 //枚舉這個種類的物品的個數 
	  for(int j=m;j>=w[k];j--)//枚舉揹包容量
	  	dp[j]=max(dp[j],dp[j-w[k]]+c[k]);
	 	//狀態轉移方程 
	cout<<dp[m];
	return 0;
}

多重揹包優化:二進制優化

在一些狀況中,咱們若是把多重揹包當01揹包來處理,數量太多了,這時咱們就須要二進制優化。
二進制優化就是把多個同樣的物品分爲1個一組,2個一組,4個一組……n個一組,再把剩下來的搞成一組。
咱們都知道咱們能夠用2的\(1\) ~ \(n\)次冪表示\(1\) ~ \(2^{n+1}-1\)的數,因此這樣作是可行的。
例題
代碼:

#include<cmath>
#include<cstdio>
#include<algorithm>
#define rg register
using namespace std;
int T,n,m,mx,cnt,tmp;
int a[2005],v[2005];
int t[500005];
bool dp[500005];
inline int read()
{
    int x=0;int f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    return x*f;
}
int main()
{
	n=read(),m=read();
	for(rg int i=1;i<=n;i++)
	{
		int aa,vv;
		aa=read(),vv=read();
		for(rg int j=1;vv-j>=0;j*=2)
		{
			a[++cnt]=j*aa;
			vv-=j;
		}
		if(vv>0) a[++cnt]=vv*aa;
	}
	for(rg int i=1;i<=m;i++)
	{
		t[i]=read();
		if(t[i]>mx) mx=t[i];
	}
	dp[0]=true;
	for(rg int i=1;i<=cnt;i++)
	{
		while(dp[tmp]&&tmp<=mx) tmp++;
		if(tmp>=mx) break;
		int nd=max(tmp,a[i]);
		for(rg int j=mx;j>=nd;j--)
			if(dp[j-a[i]]) dp[j]=true;
	}
	for(rg int i=1;i<=m;i++)
		if(dp[t[i]]) printf("Yes\n");
		else printf("No\n");
	return 0;
}

分組揹包

分組揹包,就是把東西分紅t組,每組最多取1個,最少不取,求最大價值。

咱們能夠把每組當作同樣物品,只不過它的體積和價值是會變的,咱們只要像作01揹包那樣,最後再循環判斷每組裏的物品就好了。

代碼:

#include<bits/stdc++.h>
using namespace std;
int n,m,t;
//n是物品數,m是揹包容量,t是組數
int a[15][505];
//aij表示第i組的第j個物品的編號 
int w[505],c[505];
//wi表示第i個物品的重量,ci表示價值 
int dp[6005];
int main()
{
	cin>>m>>n>>t;
	for(int i=1;i<=n;i++)
	{
		int p;
		cin>>w[i]>>c[i]>>p;
		a[p][++a[p][0]]=i;//存儲 
	}
	for(int i=1;i<=t;i++)//枚舉每組物品 
	 for(int j=m;j>=0;j--)//枚舉揹包容量 
	  for(int k=1;k<=a[i][0];k++)
	  //枚舉每組中的每一個物品 
	   if(j>=w[a[i][k]])//判斷是否能夠放下這個東西 
	 	dp[j]=max(dp[j],dp[j-w[a[i][k]]]+c[a[i][k]]);
	 	//狀態轉移方程 
	cout<<dp[m];
	return 0;
}

固然混合揹包中的多重揹包也能用二進制優化啦~
例題
代碼:

#include<cstdio>
using namespace std;
int n,m,t;
//n是物品數,m是揹包容量 
int w[100005],c[100005];
//wi表示第i個物品的重量,ci表示價值
bool p[100005];
//pi表示狀態 
int dp[1005];
int tsh,tsm,teh,tem;
int max(int x,int y){return x>y?x:y;}
int main()
{
	scanf("%d:%d %d:%d %d",&tsh,&tsm,&teh,&tem,&n);
	tsm+=tsh*60,tem+=teh*60;
	m=tem-tsm;
	for(int i=1;i<=n;i++)
	{
		int ww,cc,pp;
		scanf("%d%d%d",&ww,&cc,&pp);
		if(pp)
		{
			for(int j=1;pp-j>=0;j*=2)
			{
				t++;
				w[t]=j*ww;
				c[t]=j*cc;
				p[t]=true;
				pp-=j;
			}
			if(pp)
			{
				t++;
				w[t]=pp*ww;
				c[t]=pp*cc;
				p[t]=true;
			}
		}
		else
		{
			t++;
			w[t]=ww;
			c[t]=cc;
			p[t]=0;
		}
	}
	for(int i=1;i<=t;i++)//枚舉每一個物品 
		if(p[i])//若是是01 
			for(int j=m;j>=w[i];j--)//枚舉揹包容量 
			dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
			//狀態轉移方程 
		else
			for(int j=w[i];j<=m;j++)//枚舉揹包容量
				dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
				//狀態轉移方程 
	printf("%d\n",dp[m]);
	return 0;
}

混合揹包(01揹包+徹底揹包+多重揹包)

混合揹包,就是將多種揹包混合在一塊兒,先看題:混合揹包

那麼這種狀況,咱們能夠分類討論。回望以前的01揹包和徹底揹包的代碼,咱們會發現,只有第二重循環的順序不一樣 (廢話,就是隻改了哪裏) 那麼咱們就能夠在第二重循環前判斷便可。什麼?你不知道哪一個是徹底揹包哪一個是01揹包?搞個數組標記不就好了嘛。
而後來看多重揹包,那這個更好解決了!只要在輸入時預處理,關鍵部分根本沒變。

代碼:

#include<bits/stdc++.h>
using namespace std;
int n,m,t;
//n是物品數,m是揹包容量 
int w[6005],c[6005],p[6005];
//wi表示第i個物品的重量,ci表示價值
//pi表示狀態 
int dp[1005];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		int ww,cc,pp;
		cin>>ww>>cc>>pp;
		if(pp)
		 for(int j=1;j<=pp;j++)
		 {
		 	t++;
		 	w[t]=ww;
		 	c[t]=cc;
		 	p[t]=1;
		 }
		else
		{
			t++;
			w[t]=ww;
			c[t]=cc;
			p[t]=0;
		}
	}
	for(int i=1;i<=t;i++)//枚舉每一個物品 
	 if(p[i])//若是是01 
	  for(int j=m;j>=w[i];j--)//枚舉揹包容量 
	 	dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
	 	//狀態轉移方程 
	 else
	  for(int j=w[i];j<=m;j++)//枚舉揹包容量
	     dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
	 	//狀態轉移方程 
	cout<<dp[m];
	return 0;
}

持續更新中……只要這個蒟蒻學了新的揹包類問題,就會更新。

相關文章
相關標籤/搜索