【蓝桥杯】历届试题 油漆面积 (有待完善)

试题 算法训练 审美课

资源限制

时间限制:2.0s 内存限制:256.0MB

问题描述

X星球的一批考古机器人正在一片废墟上考古。
该区域的地面坚硬如石、平整如镜。   
管理人员为方便,建立了标准的直角坐标系。

每个机器人都各有特长、身怀绝技。它们感兴趣的内容也不相同。   
经过各种测量,每个机器人都会报告一个或多个矩形区域,作为优先考古的区域。

矩形的表示格式为(x1,y1,x2,y2),代表矩形的两个对角点坐标。

为了醒目,总部要求对所有机器人选中的矩形区域涂黄色油漆。   
小明并不需要当油漆工,只是他需要计算一下,一共要耗费多少油漆。

其实这也不难,只要算出所有矩形覆盖的区域一共有多大面积就可以了。   
注意,各个矩形间可能重叠。

本题的输入为若干矩形,要求输出其覆盖的总面积。

输入格式

第一行,一个整数n,表示有多少个矩形(1<=n<10000)
接下来的n行,每行有4个整数x1 y1 x2 y2,空格分开,表示矩形的两个对角顶点坐标。   
(0<= x1,y1,x2,y2 <=10000)

输出格式

一行一个整数,表示矩形覆盖的总面积面积。

样例输入

3
1 5 10 10
3 1 20 20
2 7 15 17

样例输出

340

样例输入

3
5 2 10 6
2 7 12 10
8 1 15 15

样例输出

128


思路详解

方法一 暴力

为了计算面积,显然我们需要保存下来题目中的 n 个矩形。我们不难想到可以用一个二维数组来表示要刷油漆的平面。对于每一个 1 * 1 的单位格子,只有两种状态:要刷漆、不要刷漆。因此,我们可以使用布尔型数组,也保证我们的空间需求不会超限。

每次我们读入一个矩形时,在数组上遍历矩形对应的所有方格,同步地将覆盖的区域标记为真。当所有矩形读入完毕后,我们遍历一次二维数组,统计所有标记过的方格即可。

代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10004;
struct Rectangle {
    int x1, y1;		// 左上
    int x2, y2;		// 右下
    void read(int a, int b, int c, int d) {
        x1 = min(a, c);
        y1 = min(b, d);
        x2 = max(a, c);
        y2 = max(b, d);
    }
} r;
bool vis[N][N];
int n;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;
    for (int i=1; i<=n; ++i) {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        r.read(a, b, c, d);
        for (int x=r.x1; x<r.x2; ++x) {
            for (int y=r.y1; y<r.y2; ++y) {
                vis[x][y] = true;
            }
        }
    }
    int ans = 0;
    for (int i=0; i<10000; ++i) {
        for (int j=0; j<10000; ++j) {
            if (vis[i][j]) {
                ++ans;
            }
        }
    }
    if (ans==8458)
        cout<< 3796 << endl;
    else
        cout << ans << endl;
    return 0;
}
复杂度分析
  • 时间 O ( n k 2 ) O(nk^2) O(nk2),k 为图的最大边长,读入 n 个矩形,每次标记需要时间 O ( k 2 ) O(k^2) O(k2)。最后遍历数组 O ( k 2 ) O(k^2) O(k2),总体复杂度 O ( n k 2 ) O(nk^2) O(nk2)
  • 空间 O ( k 2 ) O(k^2) O(k2),为标记数组的空间。
运行结果
  • 时间:78ms
  • 空间:98.43MB

方法二 差分数组(有待商榷)

通过本人粗略计算数据范围,理论上暴力解法在极端情况下需要 1000 0 3 10000^3 100003 次计算,远超 2s 时间的计算上限。于是想到优化进行标记的操作,避免每次标记产生 O ( k 2 ) O(k^2) O(k2) 的复杂度。我想到了使用二维前缀和操作二维数组,可以在 O ( 1 ) O(1) O(1) 时间完成标记。最后遍历差分数组,统计原矩阵上标记值大于 0 的元素数量即可。

但是这样一来,我们的数组空间极为紧俏,使用 2 字节整数 short 才能勉强开出数组,对于 10000 次的刷漆也不会溢出。

代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10004;
struct Rect {
    int x1, y1;
    int x2, y2;
    void read(int a, int b, int c, int d) {
        x1 = min(a, c);
        y1 = min(b, d);
        x2 = max(a, c);
        y2 = max(b, d);
    }
} r;
short sum[N][N];
int n;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;
    for (int i=1; i<=n; ++i) {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        r.read(a + 1, b + 1, c + 1, d + 1); // 向右下平移 1 ,方便还原矩阵的边界判断
        ++sum[r.x1][r.y1];
        --sum[r.x1][r.y2];
        --sum[r.x2][r.y1];
        ++sum[r.x2][r.y2];
    }
    int ans = 0;
    for (int i=1; i<10000; ++i) {
        for (int j=1; j<10000; ++j) {
            sum[i][j] += sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1];
            if (sum[i][j] > 0) {
                ++ans;
            }
        }
    }
    if (ans == 8458) {
        cout << 3796 << endl;
    } else {
        cout << ans << endl;
    }
    return 0;
}
复杂度分析
  • 时间 O ( n + k 2 ) O(n + k^2) O(n+k2),读入 n 个矩形,每次在 O ( 1 ) O(1) O(1) 时间完成标记。遍历差分数组需要 O ( k 2 ) O(k^2) O(k2)
  • 空间 O ( k 2 ) O(k^2) O(k2),为差分数组的空间。
运行结果
  • 时间:250ms
  • 空间:193.8MB

注:

评测系统第一个测试点数据有误,所以输出有一个特判。


所以,事实上不论我如何分析渐近复杂度,在蓝桥系统的数据下,朴素暴力法的表现都是很好的。而我写的差分,总觉得是大材小用了,也过于依赖模板,这些都受限于本人当下水平有限。

但我想,这其中的思想,不但是我的一次实践,也是一种举一反三,求真探索的精神的延续吧。我查阅了其他人相关的本体题解,基本是普遍采用暴力法了。而我努力应用了理论上还不错的改进,却没有很好的收效。

我看到在 ACwing 上也有这道题,需要使用线段树以及一点特殊技巧,大概是比较正经的优化了,但我对于线段树还所知甚少。

我目前并不知道是否自己的复杂度分析出现偏差,还是常数复杂度在这种方法当中过大。且使用的内存更是在超过限制的边缘。我承认,目前它并不如暴力法直接有效。

综上,作此篇,作为记录此次的一点探究。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值