📜  给定矩形的正好k个切割可以得到的最小可能面积中的最大值(1)

📅  最后修改于: 2023-12-03 15:41:17.404000             🧑  作者: Mango

给定矩形的正好k个切割可以得到的最小可能面积中的最大值

问题描述

给定一个矩形,和一个整数k。将矩形沿着某一条水平或垂直的边进行一刀,将其分成两个矩形。可以进行k次切割。要求切完后所有子矩形的面积和最小,求该最小值。

问题分析

这是一道典型的二分答案加贪心的题目。首先我们二分答案,假设当前二分的答案为x,则问题转化为:是否存在一种划分方式,使得每个子矩形的面积都小于等于x,且划分次数不大于k。

对于判断是否存在这样的划分方式,可以使用贪心的思想。考虑如何尽量使切割后的子矩形面积都小于等于x,可以发现如果在矩形中心垂直地切一刀,会将矩形分成两个尽量相似的子矩形。如果其中某个子矩形的面积大于x,则可以将其继续切割,此时划分次数+1。注意到切割后的矩形较小,因此可以保证划分次数不超过k。对所有子矩形进行上述操作之后,如果划分次数小于等于k,则表明当前答案x可行。

时间复杂度

对于一个二分答案加贪心的问题,其时间复杂度通常为O(答案空间*log(判定一个答案是否可行的复杂度))。对于本题,答案空间为矩形面积,判定一个答案是否可行的操作需要对每个子矩形进行一次划分,因此其复杂度为O(n * log(S)),其中n为矩形较长的一条边的长度,S为矩形面积。

代码实现

以下是一个基于C++11的实现:

#include <cstdio>
#include <algorithm>
#include <utility>
#include <vector>
using namespace std;

const int N = 105;

int n, m, k;
int x[N], y[N];

int main() {
    scanf("%d%d%d", &n, &m, &k);
    x[0] = y[0] = 0, x[n + 1] = m, y[m + 1] = n;
    for (int i = 1; i <= n; i++) scanf("%d", x + i);
    for (int i = 1; i <= m; i++) scanf("%d", y + i);
    while (k--) {
        int xmax = 0, ymax = 0, t = 0;
        for (int i = 0; i <= n; i++) { // 找到x数组中相邻两个元素之差最大的位置
            if (x[i + 1] - x[i] > xmax) xmax = x[i + 1] - x[i], t = i;
        }
        for (int i = 0; i <= m; i++) { // 找到y数组中相邻两个元素之差最大的位置
            if (y[i + 1] - y[i] > ymax) ymax = y[i + 1] - y[i], t = ~i;
        }
        if (xmax >= ymax) { // 按照x[t]切割
            int t1 = x[t], t2 = x[t + 1];
            for (int i = 0; i <= m; i++) y[i] = min(y[i], t1); // 更新y数组
            for (int i = t + 1; i <= n; i++) x[i] -= t2 - t1; // 更新x数组
            n--;
        } else { // 按照y[~t]切割
            int t1 = y[~t], t2 = y[~t + 1];
            for (int i = 0; i <= n; i++) x[i] = min(x[i], t1); // 更新x数组
            for (int i = ~t + 1; i <= m; i++) y[i] -= t2 - t1; // 更新y数组
            m--;
        }
    }
    long long res = 1LL * n * m;
    printf("%lld\n", res);
    return 0;
}

上述代码中,~i表示按位取反,即将i的二进制表示的每一位取反。在本题中,其作用是将y数组下标与x数组下标分开,用于区分是按照x数组还是y数组进行切割。