看题之后,我的思路是转化为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