hduoj_2844(dp,背包问题)

看题之后,我的思路是转化为01背包问题。

代码通过样例,但是tle。

#include <stdio.h>
#include <string.h>

int dp[100001];

int pre[101];

int value[100001];

int main()
{
	int n, m;
	int i, j;
	int k, p;
	int sum;
	int cnt;
	while (scanf("%d %d", &n, &m) && (n || m))
	{
	        for (i = 1; i <= n; i++)
			scanf("%d", &pre[i]);

		sum = 0;
		k = 1;
		for (i = 1; i <= n; i++)
		{
			scanf("%d", &p);
			sum += p*pre[i];
			while (p--)
				value[k++] = pre[i];
		}
		
		memset(dp, 0, sizeof(dp));
		for (i = 1; i < k; i++)
		{
			for (j = m; j >= value[i]; j--)
				dp[j] = dp[j] > (dp[j - value[i]] + value[i]) ? dp[j] : (dp[j - value[i]] + value[i]);
		}

		cnt = 0;
		for (i = 1; i <= m; i++)
		{
			if (dp[i] == i)
				cnt++;
		}
		printf("%d\n", cnt);
	}
	return 0;
}


后来查资料,发现这种题型叫做 多重背包。

多重背包和01背包的区别是每件物品的数量不是固定的。


有一本书叫做《背包九讲》,以后有时间看一下。

总结一下可能遇到的背包问题。

01背包

完全背包

多重背包

二维背包

分组背包



01背包

入门问题。


完全背包

完全背包和01背包的区别是每件物品的数量是无限的。


有N种物品和一个容量为V的背包,每种物品都有无限件可用。

第i种物品的费用是c[i],价值是w[i]。


该问题的解决思路:

1.基本思路

  f[i][v]表示前i种物品放入一个容量为v的背包获得的最大价值。

  状态转移方程是:

  f[i][v] = max(f[i-1][v-k*c[i]]+k*w[i])

  其中 0<=k*c[i]<=v

2.该问题可以转化为01背包问题来求解。

   比较关键的一点是:第i种物品最多可选V/c[i]件。

3.在2的基础上,有一种更高效的转化方法。即二进制优化。

  把第i种物品拆成费用为c[i]*(2^k),价值为w[i]*(2^k)的若干件物品。

  其中k满足c[i]*(2^k)<V。

  该如何理解这种转化方法呢?

  任何一个正整数都可以表示为2的幂次之和。

  举一个直白一点的例子。

  假如第i件物品选了7个,

  按照原来的优化方法,是对第i件物品选择了7次;

  对于二进制优化来说,是对花费为 c[i],2*c[i],4*c[i] 的物品各选择了1次,共选择3次。 

  注意 1 + 2 + 4 = 7。

4.O(V*N)解法伪代码

for (i = 1; i <= n; i++)
{
	for (j = c[i]; j <= V; j++)
		dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
}

和01背包问题的模板不相同的是:内层循环是从c[i]到V的,是正序的。

读了一些博客,还是不能很好的理解这个问题。先记录在这里。

5.两个状态转移方程

由1中基本思路变形得到的状态转移方程是:

f[i][v] = max(f[i-1][v], f[i][v-c[i]]+w[i])

其中

f[i-1][v]对应背包中没有第i种物品的情况,

f[i][v-c[i]]+w[i]对应背包中可能已经存在第i种物品的情况。

用一维数组实现,得到

f[v] = max(f[v], f[v-c[i]]+w[i])


附几道题。

hduoj_1248

价值既是价值,又是体积。

#include <stdio.h>
#include <string.h>

int v[] = { 0,150,200,350 };
int f[350001];

int max(int a, int b)
{
	return a > b ? a : b;
}

int main()
{
	int N;
	int i, j;
	int k, l;
	scanf("%d", &k);
	for (l = 1; l <= k; l++)
	{
		scanf("%d", &N);
		memset(f, 0, sizeof(f));
		for (i = 1; i <= 3; i++)
		{
			for (j = v[i]; j <= N; j++)
				f[j] = max(f[j], f[j - v[i]] + v[i]);
		}
		printf("%d\n", N - f[N]);
	}
	return 0;
		
}


hduoj_1114

第一种方法:自己写的。样例通过。tle。

正确性有待检验。

#include <stdio.h>
#include <string.h>

int f[5000001];

int w[501];
int c[501];

int max(int a, int b)
{
	return a > b ? a : b;
}

int main()
{
	int T;
	int E, F;
	int V;
	int n;
	int i, j;
	int sum;
	int p;
	scanf("%d", &T);
	
	while (T--)
	{
		scanf("%d %d", &E, &F);
		V = F - E;

		
		scanf("%d", &n);
		sum = 0;
		for (i = 1; i <= n; i++)
		{
			scanf("%d %d", &w[i], &c[i]);
			sum += w[i] * c[i];
		}

		memset(f, 0, sizeof(f));
		for (i = 1; i <= n; i++)
		{
			for (j = w[i]; j <= sum; j++)
				f[j] = max(f[j], f[j - w[i]] + c[i]);
		}

		p = 0;
		for (i = 1; i <= sum; i++)
		{
			if (f[i] == V)
			{
				p = i;
				break;
			}
		}

		if (!p)
			printf("This is impossible.\n");
		else
			printf("The minimum amount of money in the piggy-bank is %d.\n", p);
		
	}

	return 0;
}

第2种方法:看了题解。自己一开始也想到了把max改为min,但是初始化没有想到。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX 99999999
int f[10001];
int w[501], c[501];

int min(int a, int b)
{
	return a < b ? a : b;
}

int main()
{
	int T;
	int E, F;
	int V;
	int i, j;
	int n;
	
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d %d", &E, &F);
		V = F - E;

		scanf("%d", &n);

		for (i = 1; i <= n; i++)
			scanf("%d %d", &w[i], &c[i]);

		// 这里是关键
		f[0] = 0;
		for (i = 1; i <= V; i++)
			f[i] = MAX;
	
		for (i = 1; i <= n; i++)
		{
			for (j = c[i]; j <= V; j++)
				f[j] = min(f[j], f[j - c[i]] + w[i]);
		}

		if(MAX == f[V])
			printf("This is impossible.\n");
		else
			printf("The minimum amount of money in the piggy-bank is %d.\n", f[V]);
	}
	return 0;
}

其实用INT_MAX也是可以的。下面的代码也AC了。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int f[10001];
int w[501], c[501];

int min(int a, int b)
{
	return a < b ? a : b;
}

int main()
{
	int T;
	int E, F;
	int V;
	int i, j;
	int n;
	
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d %d", &E, &F);
		V = F - E;

		scanf("%d", &n);

		for (i = 1; i <= n; i++)
			scanf("%d %d", &w[i], &c[i]);

		// 这里是关键
		f[0] = 0;
		for (i = 1; i <= V; i++)
			f[i] = INT_MAX;
	
		for (i = 1; i <= n; i++)
		{
			for (j = c[i]; j <= V; j++)
			{
				if (INT_MAX != f[j - c[i]])
					f[j] = min(f[j], f[j - c[i]] + w[i]);
			}
				
		}

		if(INT_MAX == f[V])
			printf("This is impossible.\n");
		else
			printf("The minimum amount of money in the piggy-bank is %d.\n", f[V]);
	}
	return 0;
}

关于背包问题的初始化:

1.在不超过背包容量的情况下求最大价值,都初始化为0;

2.在装满背包的情况下求最大价值,f[0] = 0,其余初始化为负无穷大;

3.在装满背包的情况下求最小价值 ,f[0] = 0,其余初始化为正无穷大。


2和3的情况类似。我们以2为例,通过状态转移方程来说明初始化的原因。

2的状态转移方程是f[j] = max(f[j], f[j-c[i]]+w[i])

j-c[i]+c[i]=j。当j=V时,背包恰好装满。

f[j]的初值是负无穷,那么f[j]在什么情况下会被第一次更新呢?

就是当f[j-c[i]]是有效值时(不是负无穷),

要使f[j-c[i]]有效,必然存在i',使得f[j-c[i]-c[i']]为有效值。

容易想到的是,只有子问题是有效值,当前问题才是有效值。

那么最基本的子问题是什么呢?是 每件物品的体积都是有效值。

这些物品的体积组合得到所有有效的体积值。即每个有效的体积值都是由物品的体积组合而来的。

当遍历结束后,如果f[V]还是负无穷,说明它在遍历的过程中没有被更新过,即不存在可行解。

否则存在可行解。


更直观的解释。

当i=1时,j=c[1]有效;

当i=2时,j=c[2]、j=c[2]+c[1](j-c[2]=c[1])有效;

当i=3时,j=c[3]、j=c[3]+c[2](j-c[3]=c[2])、j=c[3]+c[1](j-c[3]=c[1])、j=c[3]+c[2]+c[1](j-c[3]=c[2]+c[1])有效。

以此类推。


参考链接:https://www.cnblogs.com/buddho/p/7867920.html

                https://blog.csdn.net/moonspiritacm/article/details/55195416



多重背包

多重背包介于完全背包和01背包之间。


有N种物品和一个容量为V的背包,每种物品都n[i]件可用

第i种物品的费用是c[i],价值是w[i]。


1.基本思路

   f[i][v] = max(f[i-1][v-k*c[i]]+k*w[i])   0<=k<=n[i]

2.直接转化为01背包问题,把第i种物品换成n[i]件01背包中的物品,很容易理解。

3.二进制优化。

   例如将13拆成1,2,4,6。

4.O(V*N)算法

   使用单调队列实现,不会。


hduoj_2844

价值既是价值,又是体积。

#include <stdio.h>
#include <string.h>

int n, m;
int i, j;
int k;

int val[101];
int num[101];
int f[100001];

int max(int a, int b)
{
	return a > b ? a : b;
}

void zeroOnePack(int value)
{
	for (j = m; j >= value; j--)
		f[j] = max(f[j], f[j - value] + value);
}

void completePack(int value)
{
	for (j = value; j <= m; j++)
		f[j] = max(f[j], f[j - value] + value);
}

int main()
{
	int cnt;
	while (EOF != scanf("%d %d", &n, &m) && (n || m))
	{
		for (i = 1; i <= n; i++)
			scanf("%d", &val[i]);
		for (i = 1; i <= n; i++)
			scanf("%d", &num[i]);


		memset(f, 0, sizeof(f));
		for (i = 1; i <= n; i++)
		{
			if (num[i] * val[i] >= m)
				completePack(val[i]);
			else
			{
				k = 1;
				while (k < num[i])
				{
					zeroOnePack(k*val[i]);
					num[i] -= k;
					k *= 2;
				}
				zeroOnePack(num[i] * val[i]);
			}
		}
		
		cnt = 0;
		for (i = 1; i <= m; i++)
		{
			if (f[i] == i)
				cnt++;
		}
		printf("%d\n", cnt);

	}
	return 0;
}

   

二维背包 分组背包

以后学习。


参考链接:https://blog.csdn.net/wumuzi520/article/details/7014830

                 https://blog.csdn.net/ppp_1026hc/article/details/52138025

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值