[Codeforces] VK Cup 2022 E. Rectangle Shrinking | 贪心,模拟

97 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情

[Codeforces] VK Cup 2022 E. Rectangle Shrinking | 贪心,模拟

题目链接

codeforces.com/contest/178…

题目

Codeforces-VK_Cup_2022-E.png

题目大意

在2 * 1e9的背景板内有一些小矩形,我们对每个小矩形可以做以下两种操作之一:

  1. 删去该小矩形
  2. 将小矩形变成它的一个子矩形(子矩形可以是小矩形本身)

问对所有矩形进行操作后并满足所有留下的矩形没有重叠面积的情况下,最大的覆盖面积是多少,并给出对应操作方案(输出每个矩形的左上和右下端点,如果是删除操作则0 0 0 0)

思路

首先,我们贪心地猜测,是不是当前覆盖的所有面积都可以保留下来。

矩形最多只有两行,那么实际只有三种情况:

  • 在上面一行
  • 在下面一行
  • 横跨两行

如果没有第三种情况(横跨两行),那么其实就是一维线段的区间覆盖问题,给出方案应该不难:

对线段的(l, r)进行排序,只有以下三种情况,从左到右遍历处理即可

Codeforces-VK_Cup_2022-一维线段.png

首先,我们可以针对上行、下行、两行的三类情况采用类似一维线段的方式去除同类矩形之间的重复面积。

接下来,我们要处理不同类的重复,其实就是单行和两行矩形之间的重复

Codeforces-VK_Cup_2022-例子.png

针对上面的三种情形,由于子矩形的子矩形还是原本矩形的子矩形,因此我们对单行矩形再做子矩形操作,重复的部分让两行矩形占有即可

最后还有以下的一种特殊情形:

Codeforces-VK_Cup_2022-特例.png

这时候我们不能再对单行矩形取子矩形了,因为无论取左还是右都会损失另一部分的面积,因此我们考虑将重复部分分配给单行矩形,两行矩形先取成只含下面一行的子矩形,然后依然根据下面一行的单行矩形情况处理它的左右边界(l, r)。

最后,这道题的思路尚可,不难想到,关键在于写代码的部分,需要注意边界是否取等等实现细节。在代码里加了一些注释,供参考

代码

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
using namespace std;
​
const int maxn = 2e6 + 10;
const int mod = 998244353;
const int inf = 1LL << 60;#define db 0
#define ttest if (db) 
​
// 子矩形结点
struct node {
    int u, l, d, r;
};
​
// 抽象出的一维线段,id对应于原始子矩形的下标
struct seg {
    int l, r, id;
    bool operator < (const seg& rhs) const {
        if (l != rhs.l) return l < rhs.l;
        return r < rhs.r;
    }
};
​
​
void solve() {
    int n;
    cin >> n;
    vector<node> a(n);
    // 抽象出的上行、下行、两行矩形的线段形式
    vector<seg> up, dn, bo; 
    for (int i = 0; i < n; ++i) {
        cin >> a[i].u >> a[i].l >> a[i].d >> a[i].r;
        if (a[i].u == a[i].d) {
            if (a[i].u == 1) up.push_back({a[i].l, a[i].r, i});
            else dn.push_back({a[i].l, a[i].r, i});
        } else {
            bo.push_back({a[i].l, a[i].r, i});
        }
    }  
​
    // op操作是对相交的线段拆分成两两不交的线段,并在原矩形上做修改
    function<void(vector<seg>&)> op = [&](vector<seg>& v) -> void {
        int sz = v.size();
        int pre = 0;
        sort(v.begin(), v.end());
        for (int i = 0; i < sz; ++i) {
            if (pre >= v[i].r) {
                v[i].l = v[i].r = 0;
                a[v[i].id] = {0, 0, 0, 0};
            } else if (pre >= v[i].l) {
                v[i].l = pre + 1;
                a[v[i].id].l = pre + 1;
            }
            pre = max(pre, v[i].r);
        }
        // 重新排序把 l = r = 0的删去线段放到前面
        sort(v.begin(), v.end());
    };
​
    // 三类情况类内去除重复部分
    op(up);
    op(dn);
    op(bo);
​
    // 先跳过删除掉的线段部分 (l = r = 0)
    int len1 = up.size(), len2 = dn.size();
    int p1 = 0, p2 = 0;
    while (p1 < len1 && up[p1].l == 0) ++p1;
    while (p2 < len2 && dn[p2].l == 0) ++p2;
 
    for (int i = 0; i < (int)bo.size(); ++i) {
​
        if (bo[i].l == 0) continue; // 跳过删除的线段部分
        bool cv1 = true, cv2 = true; // 记录是否选取该行
        
        // 两行矩形记为L, R,单行矩形记录为l, r
        while (p1 < len1 && up[p1].r < bo[i].l)  ++p1;
        while (cv1 && p1 < len1 && up[p1].l <= bo[i].r) {
            if (up[p1].r > bo[i].r && up[p1].l < bo[i].l) { // l L R r
                cv1 = false;
                break;
            } else if (up[p1].r > bo[i].r && up[p1].l >= bo[i].l) { // L l R r
                up[p1].l = bo[i].r + 1;
                a[up[p1].id].l = bo[i].r + 1;
            } else if (up[p1].r <= bo[i].r && up[p1].l < bo[i].l) { // l L r R
                up[p1].r = bo[i].l - 1;
                a[up[p1].id].r = bo[i].l - 1;
                ++p1; 
            } else if (up[p1].r <= bo[i].r && up[p1].l >= bo[i].l) { // L l r R
                up[p1].l = up[p1].r = 0;
                a[up[p1].id] = {0, 0, 0, 0};
                ++p1;
            }
        }
        
        // 类似上面
        while (p2 < len2 && dn[p2].r < bo[i].l)  ++p2;
        while (cv2 && p2 < len2 && dn[p2].l <= bo[i].r) {
            if (dn[p2].r > bo[i].r && dn[p2].l < bo[i].l) {
                cv2 = false;
                break;
            } else if (dn[p2].r > bo[i].r && dn[p2].l >= bo[i].l) {
                dn[p2].l = bo[i].r + 1;
                a[dn[p2].id].l = bo[i].r + 1; 
            } else if (dn[p2].r <= bo[i].r && dn[p2].l < bo[i].l) {
                dn[p2].r = bo[i].l - 1;
                a[dn[p2].id].r = bo[i].l - 1; 
                ++p2;
            } else if (dn[p2].r <= bo[i].r && dn[p2].l >= bo[i].l) {
                dn[p2].l = dn[p2].r = 0;
                a[dn[p2].id] = {0, 0, 0, 0};
                ++p2;
            }
        }
       
        // 判断两行矩形的行边界
        if (!cv1 && cv2) {
            a[bo[i].id].u = 2;
        } else if (cv1 && !cv2) {
            a[bo[i].id].d = 1;
        } else if (!cv1 && !cv2) {
            a[bo[i].id] = {0, 0, 0, 0};
        }
    }
    // 处理后矩形两两不交,直接对所有矩形的面积求和即可
    int sum = 0;
    for (int i = 0; i < n; ++i) {
        if (a[i].l)
            sum += (a[i].r - a[i].l + 1) * (a[i].d - a[i].u + 1);
    }
    cout << sum << '\n';
    for (int i = 0; i < n; ++i) {
        node& nd = a[i];
        cout << nd.u << ' ' << nd.l << ' ' << nd.d << ' ' << nd.r << '\n';
    }
}
​
​
void refresh() {
    
}
    
​
signed main() {
    IOS
    int t;
    t = 1;
    cin >> t;
    
    while (t--) {
        solve();
        refresh();
    }
    return 0;
}