12.49 P6228 汤姆的餐厅 题解
P6228 汤姆的餐厅
题面
Tom's Kitchen 是一家非常受欢迎的餐厅,其受欢迎的原因之一是每份菜都由至少 $ K $ 名厨师进行准备。今天有 $ N $ 份菜需要准备,第 $ i $ 份菜的准备时间是 $ A_i $ 小时。
Tom 可以雇佣 $ M $ 名厨师,厨师 $ j $ 最多可以工作 $ B_j $ 小时。此外,即使厨师 $ j $ 的工作时间不到 $ B_j $ 小时,他也会得到工作 $ B_j $ 小时的报酬。一名厨师可以花不同的时间准备不同的菜,但是每一道菜都需要由至少 $ K $ 名厨师准备,并且他们花的时间总和恰好为 $ A_i $。当一名厨师准备菜品时,他总会花正整数小时。
Tom 需要一套最优的雇佣厨师方案,以使得厨师不工作就获得报酬的小时数(即所有雇佣厨师的总工作时间上限与准备所有菜的总时间之差)尽可能小。
$ 1 \leq N,M,K,A_i,B_j \leq 300 $。
题解
拆贡献好题
发现这道题对于每道菜有两个限制
- 共 \(k\) 个厨师做
- 一共做 \(a_i\) 时间
那么我们可以考虑分拆每个厨师的贡献,分为总价值 \(b_i\) 和基础价值 \(min (b_i, n)\)
这样就可以转化为一个背包模型,设 \(f[i][j]\) 表示考虑了前 \(i\) 个人,总价值为 \(j\) 的情况下的最大基础价值
转移方程:\(f[i][j] = max (f[i - 1][j], f[i - 1][j - b_i] + min (n, b_i))\)
时间复杂度为 \(O(m \sum b_i)\) ,空间复杂度滚动数组后为 \(O(\sum b_i)\)
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 1e5 + 10;
int n, m, k;
int a[N], b[N];
// f[i][j] : 考虑前 i 个人,总价值为 j 的情况下,最大基础价值
int f[2][90010];
int main () {
string no_solution = "Impossible";
cin >> n >> m >> k;
int suma = 0, sumb = 0;
bool fn = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (a[i] < k) {
fn = 1;
}
suma += a[i];
}
for (int i = 1; i <= m; i++) {
cin >> b[i];
sumb += b[i];
}
if (fn) {
cout << no_solution << endl;
return 0;
}
//这里要将不合法状态初始化为 -inf ,因为我们要求 f[i][j] 为总价值为 j 而不能小于 j, 也就是保证背包要装满
memset (f, -0x3f, sizeof f);
f[0][0] = 0;
for (int i = 1; i <= m; i++) {
int now = i & 1, la = now ^ 1;
for (int j = 0; j < b[i]; j ++) {
f[now][j] = f[la][j];
}
for (int j = b[i]; j <= sumb; j++) {
f[now][j] = max (f[la][j], f[la][j - b[i]] + min (n, b[i]));
}
}
for (int i = suma; i <= sumb; i ++) {
if (f[m & 1][i] >= n * k) {
cout << i - suma << endl;
return 0;
}
}
cout << no_solution << endl;
return 0;
}