CF2000F Color Rows and Columns
题解
知识点:贪心、背包dp。
先考虑一个矩形的情况:
- 若是方形,行列交替染色最优。
- 若不是方形,选行列中较小的一侧染色,直到变为方形。
因此,我们可以根据上面的结论预处理 \(c_{i,j}\) ,表示第 \(i\) 个矩形贡献为 \(j\) 的最小花费。
现在考虑多个矩形的情况,显然是一个分组背包问题。每个矩形为一个组,第 \(i\) 个矩形有 \(a_i + b_i + 1\) 个项(包括不选) ,第 \(j\) 项贡献为 \(j\) 花费是 \(c_{i,j}\) ,每组只能选一个。
我们设 \(f_{i,j}\) 表示前 \(i\) 个矩形贡献为 \(j\) 时的最小花费。特别地,根据题意我们采用一个技巧,定义 \(f_{i,k}\) 表示前 \(i\) 个矩形贡献至少为 \(k\) 时的最小花费。
计算花费有两种方法,打表法和刷表法,前者寻找前导状态更新当前状态,后者寻找后继状态更新后继状态,采用上述技巧后更适合采用刷表法。打表法实现和效率都会更复杂,因为 \(f_{i, k}\) 的前导状态将变为 \(\sum w_{i,j} = \sum_{j = 0}^{a_i + b_i} j\) ,而非 \(j\) 个。
初始化正无穷,\(f_{0, 0} = 0\) ,转移方程如下:
for (int i = 1;i <= n;i++) {
for (int j = 0;j <= k;j++) {
for (int id = 0;id <= a[i] + b[i];id++) {
f[i][min(k, j + id)] = min(f[i][min(k, j + id)], f[i - 1][j] + c[i][id]);
}
}
}
此外,如果采用单行滚动数组优化,第二层循环应变为逆序,且第二层和第三层循环不能交换。
当然,也可以采用两行数组实现滚动数组,更方便。
时间复杂度 \(O(nk\max\{ a_i, b_i\})\)
空间复杂度 \(O(n(k+\max\{ a_i, b_i\}))\)
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int a[1007], b[1007];
int c[1007][207]; // 第 i 个矩形贡献为 j 的最小花费
int f[1007][107]; // 前 i 个矩形贡献为 j 的最小花费,分组背包
bool solve() {
int n, k;
cin >> n >> k;
for (int i = 1;i <= n;i++) {
cin >> a[i] >> b[i];
int delta = abs(a[i] - b[i]);
// 短边先填,直至方形
for (int j = 1;j <= delta;j++) c[i][j] = c[i][j - 1] + min(a[i], b[i]);
// 方形行列交替填
for (int j = delta + 1; j <= a[i] + b[i];j++) c[i][j] = c[i][j - 1] + min(a[i], b[i]) - (j - delta) / 2;
}
for (int i = 0;i <= n;i++)
for (int j = 0;j <= k;j++)
f[i][j] = 1e9;
// 建议刷表法,因为 fi,k 表示前 i 个矩形贡献 >= k 的最小花费,填表法在 fi,k 的处理会更复杂,效率也更低
f[0][0] = 0;
for (int i = 1;i <= n;i++) {
for (int j = 0;j <= k;j++) {
for (int id = 0;id <= a[i] + b[i];id++) {
f[i][min(k, j + id)] = min(f[i][min(k, j + id)], f[i - 1][j] + c[i][id]);
}
}
}
cout << (f[n][k] > 1e8 ? -1 : f[n][k]) << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/18427520