多重背包例题:庆功会
多重背包例题:庆功会
朴素版,二进制优化,单调队列优化三种写法。
原题链接:https://www.acwing.com/problem/content/1021/
补充
dp思路
多重背包dp
dp
状态表示
集合:f[i][j]表示从前i种物品中选,价格不超过j的所有方案的集合
属性:max 最大价值
状态转移
[0,1,2,...,s[i]]
0 <= k <= s[i]
f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i])
代码
普通多重背包
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510,M = 6010;
int v[N],w[N],s[N];
int n,m;
int f[N][M];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++) cin >> v[i] >> w[i] >> s[i];
for(int i = 1;i <= n;i ++)
{
for(int j = 1; j <= m;j ++)
{
for(int k = 0; k * v[i] <= j && k <= s[i]; k ++) // 这里k*v[i] <= j && k<=s[i]两个条件容易写出错
f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
cout << f[n][m];
return 0;
}
二进制优化多重背包
#include<iostream>
#include<algorithm>
using namespace std;
const int M = 60100;
int v[M],w[M],cnt;
int n,m;
int f[M];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++)
{
int a,b,s; // 物品体积 价值 数量
scanf("%d%d%d",&a,&b,&s);
// 将这s个物品拆开成logs组(二进制优化法):1 2 4 8 16... ...
int k = 1;
while(k < s)
{
cnt ++;
v[cnt] = k * a;
w[cnt] = k * b;
s -= k; // 记得从s中减去k
k *= 2;
}
if(s > 0)
{
cnt ++;
v[cnt] = s * a;
w[cnt] = s * b;
}
}
n = cnt; // 其实物品数量变成了cnt个
// 01背包
for(int i = 1;i <= n;i ++)
{
for(int j = m; j >= v[i]; j --)
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
cout << f[m];
return 0;
}
单调队列优化多重背包
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 510,M = 6010;
int v[N],w[N],s[N];
int f[M],g[M];
int q[M]; // 队列长度一定要开成体积大小(j的范围此题中是价格的范围)
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++) cin >> v[i] >> w[i] >> s[i];
for(int i = 1;i <= n;i ++)
{
memcpy(g,f,sizeof g);
for(int r = 0; r < v[i]; r ++)
{
int head = 0,tail = -1;
for(int j = r; j <= m;j += v[i])
{
// 判断队头是否滑出
if(head <= tail && q[head] < j-s[i]*v[i]) head ++;
// 保证模拟队列递减(队头为最大值)
while(head <= tail && g[q[tail]]+(j-q[tail])/v[i]*w[i] <= g[j]) tail --;
q[++ tail] = j;
f[j] = max(f[j],g[q[head]] + (j-q[head])/v[i]*w[i]);
}
}
}
printf("%d",f[m]);
return 0;
}
rds_blogs