背包问题

参考文献:背包九讲

一: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;
}







posted @ 2016-03-11 15:46  seasonal  阅读(102)  评论(0编辑  收藏  举报