各类背包问题题型总结
混合背包问题
有 N 种物品和一个容量是 V的背包。
物品一共有三类:
第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
解法:将各种背包利用二进制优化的思想存为一个个单独问题,再按01背包问题求解即可
for (int i = 1; i <= n; i++)
{
int v, w, s; cin >> v >> w >> s;
if (s == -1)
s = 1;
if (s == 0)
s = m / v;
int k = 1;
while (k <= s)
vv[cnt] = k * v;ww[cnt] = k * w;cnt++;s -= k;k*=2;
if (s)
vv[cnt] = v * s;ww[cnt] = w * s;cnt++;
}
二维花费的背包问题
有 N件物品和一个容量是 V的背包,背包能承受的最大重量是 M
每件物品只能用一次。体积是 vi重量是 mi价值是 wi
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
解法,增加了一维的费用就增加一层for循环即可
参考代码:
for (int i = 1; i <= n; i++)
{
for (int j = v; j >= vv[i]; j--)
{
for (int k = m; k >= mm[i]; k--)
{
f[j][k] = max(f[j][k], f[j - vv[i]][k - mm[i]] + ww[i]);
}
}
}
二位花费下的大于给定体积的最小价值
for(int i =1;i<=n;i++)
{
for(int j= v1;j>=0;j--)
{
for(int k = v2;k>=0;k--)
{
f[j][k] = min(f[j][k],f[max(j-vv1[i],0)][max(k-vv2[i],0)]+w[i]);
}
}
}
求最优方案的个数(能得到最大价值的方案有几种)
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= v[i]; j--)
{
if (f[j - v[i]] + w[i] > f[j])//如果发现选了当前物品后,j体积下的最大价值变大了,那么就更新最大价值
//以及j体积最大价值对应的方案数;
{
f[j] = f[j - v[i]]+w[i];
num[j] = num[j-v[i]];//num[j]指的是前i-1个物品中得到j价值的方案个数
}
else if (f[j - v[i]] + w[i] == f[j])
{
num[j] = (num[j - v[i]] + num[j]) % mod;//在这段代码更新num[j]之前,num[j]的所有方案都不选当前物品
//而num[j-v[i]]的这些方案现在都选了当前物品,这些方案数的最大价值相同,所以合并到一块
}
}
}
memcpy的使用(插入知识点)
函数原型
void *memcpy(void*dest, const void *src, size_t n);
功能
由src指向地址为起始地址的连续n个字节的数据复制到以destin指向地址为起始地址的空间内。
背包问题-----求字典序最小的最优方案,同时输出这个方案
{求动态规划的路径,需要从后往前推,判断当前状态可以从哪个状态来到达}
f[i][j]表示从第i个物品到最后一个物品,体积不大于j的最大价值
for (int i = n; i>=1; i--)
{
for (int j = 1; j <= sum; j++)
{
f[i][j] = f[i + 1][j];
if (j >= v[i])
{
f[i][j] = max(f[i][j], f[i + 1][j - v[i]] + w[i]);
}
}
}
'输出路径':从前往后遍历来保证最小字典序,如果当前物品在最优方案里面,
//那么 f[i][j] == f[i + 1][j - v[i]] +w[i]
int j = sum;
for (int i = 1; i <= n; i++)
{
if (j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i])
{
cout << i << " ";
j -= v[i];
}
}
混合背包问题下的方案数问题**
思路就是把01背包和完全背包分开求方案数
f[0] = 1; // 一枚硬币都没有的时候,只有一种就是都不选
for (int i = 1; i <= n1; i ++ ) { // 普通币:完全背包
int p; // 第i种纪念币的面值是p
cin >> p;
for (int j = p; j <= m; j ++ ) f[j] = (f[j] + f[j - p]) % MOD;
}
for (int i = 1; i <= n2; i ++ ) { // 纪念币:0/1背包
int p;
cin >> p;
for (int j = m; j >= p; j -- ) f[j] = (f[j] + f[j - p]) % MOD;
}
cout << f[m] << endl;
分组背包问题下的求最优方案,并输出这个方案(难点)(acwing1013机器分配)
重点在于如何输出这个路径
输出路径的思路:
- 用一个数组g[i][j]来记录从第一组物品到当前i组物品为止,在总体积为j的情况下,选择i组物品中的体积为多少的物品(如果没选就是0)
- 然后就是print函数,第一参数代表当前遍历到了第几组,第二个参数是从第一组到当前组一共有多少体积可以分配(注意每行代码的顺序)
- 注意调用print函数时,一开始传的参数是最后一组以及最大体积(相当于逆序遍历)
int f[N], w[N], g[N][N];
void print(int x, int y)
{
if(x==0) return ;
int k = g[x][y];
print(x-1,y-k);
printf("%d %d\n",x,k);
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= m; j ++) scanf("%d", &w[j]);
for(int j = m;j>=1;j--)
{
for(int k = 1;k<=j;k++)
{
if(f[j-k]+w[k]>f[j])
{
f[j] = f[j-k]+w[k];
g[i][j] = k;
}
}
}
}
printf("%d\n", f[m]);
print(n, m);
return 0;
}
做题经验总结
如果某种物品可以无限选,那么for循环正序进行,如果只能选一个,就逆序
如果是大于给定体积下的最小值,for循环需要到0(可参考今日昨日的博客)