ACM_模板_背包问题

背包问题是属于动态规划中的一种非常经典的运用,小编在这里大力推荐dd_engi的背包九讲,这种超级详细的讲解在小编心中是可以得到诺贝尔文学奖的~

1.01背包

首先是最基础的01背包问题,所有元素只能使用一次,此问题只需要考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物 品放入容量为v的背包中”,价值为f[i-1][v];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。

所以我们能够得到01背包的状态转移方程:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}。

 

#include //一维数组 
#include 
#include 
using namespace std;
int v[1001],p[1001];
int dp[1002];
int main()
{
	int t,n,V,i,j;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&V);
		for(i=0;i=v[i];j--)
			{
				dp[j]=max(dp[j],dp[j-v[i]]+p[i]);
			}
		printf("%d\n",dp[V]);
	}
	return 0;
}


小编在这里说明一下,01背包问题是可以用到二维数组的,但一维数组在优化上代码的简洁上,都是要比二维的要好,所以一维的使用更加广泛。

 

2.完全背包

完全背包非常像01背包,只是在元素的使用上变成了无限次使用。小编先给出完全背包的伪代码。

for i=1..N
    for v=0..V
        f[v]=max{f[v],f[v-cost]+weight}

先想想为什么在01背包中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1] [v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的 子结果f[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v=0..V的顺序循环。这就是这个简单的程序为何成立的道理。

这里小编顺便来介绍一下背包的满存储问题,这是一个很简单的道理,我们只需要把dp的初始dp[0]=0,其他元素都为-INF即可,这样最后有值而非-INF的就是达到了满存储的状态,只有一路从0这个位置开始的才是合法的。

 

#include //完全背包(满存储) 
#include 
#include 
#include 
#define maxn 10000+10
#define INF 0x3f3f3f3f
using namespace std;
int val[maxn],cost[maxn];
int dp[maxn];
int main()
{
    int t,e,f,n;
	cin>>t;
	while(t--)
	{
		cin>>e>>f;
		int V=f-e;
		cin>>n;
		for(int i=1;i<=n;i++)
		{
			cin>>val[i]>>cost[i];
		}
		memset(dp,-INF,sizeof dp);
		dp[0]=0;
		for(int i=1;i<=n;i++)
		{
			for(int j=cost[i];j<=V;j++)
			{
				dp[j]=max(dp[j],dp[j-cost[i]]+val[i]);
			}
		}
		if(dp[V]>0)printf("%d\n",dp[V]);
		else printf("This is impossible.\n");
	}
    return 0;
}

 

 

 

 

 

3.多重背包

多重背包问题其实从本质来讲就是对01背包和完全背包的灵活运用,对于那些数量*价值>总容量的元素,我们可以认为它对于背包来说是无限个的,因为怎么取都不可能把所有元素取完,那数量*价值<总容量的元素我们也可以经过灵活处理把它变成01背包问题,这里需要用到线性代数的极大线性无关组的思想,我们把数量拆成1.2.4.8……这样的,因为从二进制的角度讲由于1.2.4.8……这样的组合每一个数的二进制码都不会在同一位置重复,所以很明显这组数是可以组合出任何一组数的,所以我们就不用对这种元素一个一个的进行01背包处理,很好的提高了效率。

 

#include //多重背包 
#include 
using namespace std;
int dp[105],v,n[105],vl[105],ct[105];
void ZeroOnePack(int cost,int value)
{
    for(int i=v; i>=cost; i--)
    	dp[i]=max(dp[i],dp[i-cost]+value);
}
void CompletePack(int cost,int value)
{
	for(int i=cost; i<=v; i++)
    	dp[i]=max(dp[i],dp[i-cost]+value);
}
void MultiPack(int cost,int value,int num)
{
    int k;
    if(num*cost>=v)
    	CompletePack(cost,value);
    else
	{
        k=1;
        while(num>k)
		{ 
            ZeroOnePack(k*cost,k*value);
            num-=k;
            k*=2;
        }
		ZeroOnePack(num*cost,num*value);
	}
}
int main()
{
    int c,m,i;
    scanf("%d",&c);
    while( c--)
	{
        scanf("%d%d",&v,&m);
        for( i=1; i<=m; i++)
			scanf("%d%d%d",&vl[i],&ct[i],&n[i]);
        memset(dp,0,sizeof(dp));
        for( i=1; i<=m; i++)
            MultiPack(vl[i],ct[i],n[i]);
        printf("%d\n",dp[v]);
    }
}

 

 

 

 

 

 

 

你可能感兴趣的:(ACM_算法_模板)