P1858 多人背包
题面
01背包的前\(k\)优解
思路
我们先考虑一下最平凡的01背包问题是如何进行求解的,一维状态下
\(f[j]=max(f[j],f[j-w[i]]+v[i])\)
有第k优解的限制怎么办,不妨加一个维度,把动态规划的转移想象成图中点与点之间
的移动
\(f[j][k]\)表示装满体积为\(j\)的背包的第\(k\)优解
我们发现,最优解的数值是随着\(k\)增大而减小的,满足单调性
很容易发现,我们需要记录用其他物品来填充背包是否能得到更优解.
因此我们需要记录一个变量c1表示体积为j的时候的第c1优解能否被更新.
再去记录一个变量c2表示体积为j-v[i]的时候的第c2优解.
这样也就回到了01背包的最基本的情况,选还是不选
显然对于体积为\(j\)的情况,它的前\(k\)优解可以由以上两种情况转移过来
那么我们可以在转移的时候记录一个中间数组,\(now[]\)
所以有以下的代码
int cnt = 0;//对于每个j,我们都需要清空一下now数组
int c1 = 1;//情况1的最优解
int c2 = 1;//情况2的最优解
while (cnt <= K) {//求出前k优解
if (dp[j][c1] > dp[j - w[i]][c2] + v[i])
now[++cnt] = dp[j][c1++];//如果情况1的价值大,取出情况1的价值
else {
now[++cnt] = dp[j - w[i]][c2++] + v[i];//如果情况2的价值大,取出情况2的价值
}
}
for (int z = 1; z <= K; z++) {
dp[j][z] = now[z];//进行更新
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 5001;
int dp[5001][51];
int v[N], w[N];
int now[N];
int K, n, m;
long long ans;
int main() {
scanf("%d%d%d", &K, &m, &n);
memset(dp, -0x3f, sizeof(dp));
dp[0][1] = 0;
for (int i = 1; i <= n; i++) {
scanf("%d%d", &w[i], &v[i]);
}
for (int i = 1; i <= n; i++) {
for (int j = m; j >= w[i]; j--) {
int cnt = 0;
int c1 = 1;
int c2 = 1;
while (cnt <= K) {
if (dp[j][c1] > dp[j - w[i]][c2] + v[i])
now[++cnt] = dp[j][c1++];
else {
now[++cnt] = dp[j - w[i]][c2++] + v[i];
}
}
for (int z = 1; z <= K; z++) {
dp[j][z] = now[z];
}
}
}
for (int i = 1; i <= K; i++) ans += dp[m][i];
printf("%lld\n", ans);
return 0;
}