背包问题整理(持续更新)

多重背包

模板:
在这里插入图片描述
多重背包二进制优化:
用于多重背包问题中,一个物品可以放限制次的数量。
主体代码:在这里插入图片描述
在使用时,分别讨论a[i]*b[i]<=m,其中a[i]为数量,b[i]为权值,m为上限,这时候使用二进制优化,当a[i]*b[i]>m时,使用正常多重背包模板,这样写时间更优化。
同时,也可以在输入的过程中使用二进制优化
在这里插入图片描述
1.HDU2844 Coins(二进制优化)

#include<iostream>
#include<cstdio>
#include<string.h>
#include<vector>
using namespace std;
int n,m;
int dp[110000];
int a[1110],b[1110];
int main()
{
	while(scanf("%d%d",&m,&n))
	{
		for(int i=0;i<n;i++)
			scanf("%d%d",&a[i],&b[i]);
		for(int i=0;i<n;i++)
		{
			if(a[i]*b[i]<=m)
			{
				for(int k=1;k<=a[i];k*=2)
				{
					for(int j=m;j>=k*b[i];j--)
					{
						if(j==k*b[i]||dp[j-k*b[i]])
							dp[j]=1;
					}
					a[i]-=k;
				}
				if(a[i]>0)
				{
					for(int j=m;j>=a[i]*b[i];j--)
						if(j==a[i]*b[i]||dp[j-a[i]*b[i]])
							dp[j]=1;
				}
			}
			else
			{
				for(int j=b[i];j<=m;j++)
					if(j==b[i]||dp[j-b[i]])
						dp[j]=1;
			}
		}
		dp[0]=1;
		for(int i=m;i>=0;i--)
			if(dp[i])
			{
				cout<<i<<endl;
				break;
			}
	}
	return 0;
}

这里面的主题转移函数,也可以这样下写:
memset(dp,-0x7f,sizeof(dp));
dp[0]=0;
转移方程:
dp[j]=max(dp[j],dp[j-k*a[i]]+k);
2.POJ1276 二进制优化

#include<iostream>
#include<cstdio>
#include<string.h>
#include<vector>
using namespace std;
int n,m;
int dp[110000];
int a[15],b[15];
int main()
{
	while(~scanf("%d%d",&m,&n))
	{
		memset(dp,0,sizeof(dp));
		for(int i=0;i<n;i++)
			scanf("%d%d",&a[i],&b[i]);
		for(int i=0;i<n;i++)
		{
			if(a[i]*b[i]<=m)
			{
				for(int k=1;k<=a[i];k*=2)
				{
					for(int j=m;j>=k*b[i];j--)
					{
						if(j==k*b[i]||dp[j-k*b[i]])
							dp[j]=1;
					}
					a[i]-=k;
				}
				if(a[i]>0)
				{
					for(int j=m;j>=a[i]*b[i];j--)
						if(j==a[i]*b[i]||dp[j-a[i]*b[i]])
							dp[j]=1;
				}
			}
			else
			{
				for(int j=b[i];j<=m;j++)
					if(j==b[i]||dp[j-b[i]])
						dp[j]=1;
			}
		}
		dp[0]=1;
		for(int i=m;i>=0;i--)
			if(dp[i])
			{
				cout<<i<<endl;
				break;
			}
	}
	return 0;
}

完全背包问题

每个物品可以取无限次,有一定的空间限制,取最优
(同样也可以转化为0/1背包问题,使用二进制优化)
模板:
在这里插入图片描述
1.CF543A Writing Code
完全背包问题,转化为有多种物品,每个物品各有重量,可以放无限次,要求刚好放入n件物品,且重量小于m 的方案数
理解了dp[i][j]这个题目就好做了,表示已经写了i行代码产生j个bug数的方案数量

#include<iostream>
#include<cstdio>
using namespace std;
int dp[550][550];
int a[550];
int n,m,b,mod;
int main()
{
	scanf("%d%d%d%d",&n,&m,&b,&mod);
	for(int i=1;i<=n;i++)  scanf("%d",&a[i]);
	dp[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			for(int k=a[i];k<=b;k++)
			{
				dp[j][k]+=dp[j-1][k-a[i]];
				dp[j][k]%=mod;
			}
		}
	}
	int cnt=0;
	for(int i=0;i<=b;i++)
		if(dp[m][i])
			cnt+=dp[m][i],cnt%=mod;
	cout<<cnt<<endl;
	return 0;
}
			

		

2.CF189A Cut Ribbon
将一根长度为n的绳子分成只由a.b.c长度组成的子绳子。
完全背包问题 考虑到必须要能够正好由a.b.c拼凑出n
在完全背包中加入条件

if(dp[j-w[i]]!=0||j-w[i]==0)
			   dp[j]=max(dp[j],1+dp[j-w[i]]);
#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
int n,a,b,c;
int dp[4500];
int max(int a,int b)
{
	return a>b?a:b;
}
int main()
{
	scanf("%d%d%d%d",&n,&a,&b,&c);
	int w[3]={a,b,c};
	for(int i=0;i<3;i++)
	{
		for(int j=w[i];j<=n;j++)
		{
			if(dp[j-w[i]]!=0||j-w[i]==0)
			   dp[j]=max(dp[j],1+dp[j-w[i]]);
		}
	}
	cout<<dp[n]<<endl;
	return 0;
}

3.HDU1114 Piggy-Bank
刚好完全背包问题

#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
int w0,w1;
int v[550],w[550];
int dp[11000];
int min(int a,int b)
{
	return a<b?a:b;
}
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		scanf("%d%d%d",&w0,&w1,&n);
		m=w1-w0;
		for(int i=0;i<n;i++)
			scanf("%d%d",&v[i],&w[i]);
		for(int i=1;i<=m;i++)
			dp[i]=99999999;
		dp[0]=0;
		for(int i=0;i<n;i++)
		{
			for(int j=w[i];j<=m;j++)
			{
				if(j==w[i]||dp[j-w[i]])
					dp[j]=min(dp[j-w[i]]+v[i],dp[j]);
			}
		}
		if(dp[m]==99999999)
			printf("This is impossible.\n");
		else
			printf("The minimum amount of money in the piggy-bank is %d.\n",dp[m]);
	}
	return 0;
}

01背包问题

模板:
三种写法:
一维数组:

for(int i=0;i<n;i++)
	{
		for(int j=m;j>=thing[i].first;j--)
		{
			dp[j]=max(dp[j],dp[j-thing[i].first]+thing[i].second);
		}
	}

二维数组:

for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(j>=thing[i].first)
				dp[i][j]=max(dp[i][j],dp[i-1][j-thing[i].first]+thing[i].second);
			else
				dp[i][j]=dp[i-1][j];
		}
	}

递归+记忆化:

int package(int index,int w)
{
	if(index==0||w<=0)
		return 0;
	if(dp[index][w]!=0)
		return dp[index][w];
	//不放第index件物品
	int res=package(index-1,w);
	//放第index件物品
	if(w>=thing[index].first)
		res=max(res,package(index-1,w-thing[index].first)+thing[index].second);
	dp[index][w]=res;
	return res;
}

关于初始化的细节问题:
①要求恰好装满背包,那么在初始化时除了 F[0] 为 0,其它 F[1…V ] 均设为 −∞,这样就可以保证最终得到的 F[V ] 是一种恰好装满背包的最优解。
②如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将 F[0…V ] 全部设为 0。
1.POJ3624 http://poj.org/problem?id=3624
一维DP数组

#include<cstdio>
#include<iostream>
#include<string>
using namespace std;
int n,m;
int dp[13000];
pair<int ,int > thing[3500];
int main()
{
	cin>>n>>m;
	for(int i=0;i<n;i++)
	{
		scanf("%d%d",&thing[i].first,&thing[i].second);
	}
	for(int i=0;i<n;i++)
	{
		for(int j=m;j>=thing[i].first;j--)
		{
			dp[j]=max(dp[j],dp[j-thing[i].first]+thing[i].second);
		}
	}
	int ans=0;
	for(int j=m;j>=0;j--)
		ans=max(ans,dp[j]);
	cout<<ans<<endl;
	return 0;
}

二维DP数组:这种会超内存,就当个参考

#include<cstdio>
#include<iostream>
#include<string>
using namespace std;
int n,m;
int dp[3500][13000];
pair<int ,int > thing[3500];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&thing[i].first,&thing[i].second);
	}
	/*for(int i=0;i<n;i++)
	{
		for(int j=m;j>=thing[i].first;j--)
		{
			dp[j]=max(dp[j],dp[j-thing[i].first]+thing[i].second);
		}
	}
	int ans=0;
	for(int j=m;j>=0;j--)
		ans=max(ans,dp[j]);*/
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(j>=thing[i].first)
				dp[i][j]=max(dp[i][j],dp[i-1][j-thing[i].first]+thing[i].second);
			else
				dp[i][j]=dp[i-1][j];
		}
	}
	int ans=0;
	for(int j=1;j<=m;j++)
	{
		ans=max(dp[n][j],ans);
	}
	cout<<ans<<endl;
	return 0;
}
原创文章 65 获赞 3 访问量 2092

猜你喜欢

转载自blog.csdn.net/littlegoldgold/article/details/105507250
今日推荐