CF2000F Color Rows and Columns

题目链接

题解

知识点:贪心、背包dp。

先考虑一个矩形的情况:

  1. 若是方形,行列交替染色最优。
  2. 若不是方形,选行列中较小的一侧染色,直到变为方形。

因此,我们可以根据上面的结论预处理 \(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;
}
posted @ 2024-09-23 17:53  空白菌  阅读(5)  评论(0编辑  收藏  举报