过度种植

过度种植

农夫约翰购买了一台新机器,该机器能够在其农场的任何“轴向对齐”(即具有垂直和水平边)的矩形区域内种草。

不幸的是,这台机器有一天出了故障,并在 $N$ 个不同的矩形区域内进行了种草工作,其中一些区域可能会有重叠。

给定机器工作的具体 $N$ 个矩形区域,请你计算种上草的区域的总面积是多少。

输入格式

第一行包含整数 $N$。

接下来 $N$ 行,每行包含四个整数 $x_{1},y_{1},x_{2},y_{2}$,表示其中一个矩形区域的左上角坐标 $\left( {x_{1},y_{1}} \right)$ 和右下角坐标 $\left( {x_{2},y_{2}} \right)$。

输出格式

输出种上草的区域的总面积。

数据范围

$1 \leq N \leq 10$,
$−10000 \leq x_{1},y_{1},x_{2},y_{2} \leq 10000$,
$x_{1} < x_{2}$,
$y_{1} > y_{2}$。

输入样例:

2
0 5 4 1
2 4 6 2

输出样例:

20

 

解题思路

  如果$N$比较大的话,例如$N = 1000$,就需要用到扫描线。如果$N = 10000$还需要用到线段树来优化。但这里的$N$最大为$10$,并不需要用到那么复杂的写法。容易发现这些矩形的并集是一个不规则的图形,比较难求,但这些矩形的交集也是矩形,就比较好求了。因此我们发现并集不好求而交集好求,就可以用到容斥原理。容斥原理的公式如下:$$\left| \bigcup\limits_{1 \leq i \leq n} {S_{i}} \right| = \sum\limits_{1 \leq i \leq n} {\left| S_{i} \right|} -  \sum\limits_{1 \leq {i<j} \leq n} {\left| S_{i} \cap S_{j} \right|} + \sum\limits_{1 \leq {i<j<k} \leq n} {\left| S_{i} \cap S_{j} \cap S_{k} \right|} - \dots + {\left( -1 \right)}^{n-1}\left| \bigcap\limits_{1 \leq i \leq n} {S_{i}} \right|$$

  这样就可以把求并集的问题转换为求交集的问题。虽然交集好求,但项数很多,一共有$C_{n}^{1} + C_{n}^{2} + \dots + C_{n}^{n} = 2^{n} - 1$项,由于$N$最大只有$10$,因此最多也就只有$1023$项,再加上求所有矩形的交集,因此时间复杂度为$O \left( 2^{n} \times n \right)$。

  矩形求交集其实就是区间求交,我们把$X$轴和$Y$轴独立开来看,例如$X$轴上有两个区间$\left[ {a,b} \right]$和$\left[ {c,d} \right]$,那么这两个区间的交集就是$\left[ {max \{{a,c}\},~ min \{{b,d}\}} \right]$。同理$Y$轴的区间求交也是如此。

  我们在枚举每次求交集的矩形个数的时候(也就是枚举$C_{n}^{i}$),可以用二进制枚举。同时读入的坐标是数学坐标系,$y_{1} > y_{2}$,为了让$y_{1} < y_{2}$与$x_{1} < x_{2}$保持一致方便区间求交,我们把坐标系变成矩阵坐标系,即把左上角的点和右下角的点转化成左下角的点和右上角的点。

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 15, INF = 0x3f3f3f3f;
 5 
 6 int n;
 7 struct Node {
 8     int x1, y1, x2, y2;
 9 }p[N];
10 
11 int get(int st) {
12     // 初始定义一个无穷大的矩形
13     int x1 = -INF, y1 = -INF, x2 = INF, y2 = INF, cnt = 0;
14     for (int i = 0; i < n; i++) {
15         if (st >> i & 1) {
16             cnt++;  // 求交矩阵个数+1
17             
18             // x轴上求交
19             x1 = max(x1, p[i].x1);
20             x2 = min(x2, p[i].x2);
21             
22             // y轴上求交
23             y1 = max(y1, p[i].y1);
24             y2 = min(y2, p[i].y2);
25         }
26     }
27     
28     int ret = max(x2 - x1, 0) * max(y2 - y1, 0);    // 有可能没有交集,算出是负数,因此要与0取最大值
29     if (cnt % 2 == 0) ret *= -1;    // 如果矩形的个数为偶数,根据公式要返回负数值
30     
31     return ret;
32 }
33 
34 int main() {
35     scanf("%d", &n);
36     for (int i = 0; i < n; i++) {
37         scanf("%d %d %d %d", &p[i].x1, &p[i].y2, &p[i].x2, &p[i].y1);   // y1 < y2
38     }
39     
40     int ret = 0;
41     for (int i = 1; i < 1 << n; i++) {
42         ret += get(i);  // 获取i状态下(i的二进制有多少个1)的矩形求交的面积
43     }
44     printf("%d", ret);
45     
46     return 0;
47 }

  补充个扫描线加线段树的做法。AC代码如下,时间复杂度为$O(n \times \log{M})$:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 30, M = 2e4 + 10;
 5 
 6 struct Seg {
 7     int x, y1, y2, c;
 8 }seg[N];
 9 struct Node {
10     int l, r, cnt, len;
11 }tr[M * 4];
12 
13 void build(int u, int l, int r) {
14     if (l == r) {
15         tr[u] = {l, r, 0, 0};
16     }
17     else {
18         int mid = l + r >> 1;
19         build(u << 1, l, mid);
20         build(u << 1 | 1, mid + 1, r);
21         tr[u] = {l, r, 0, 0};
22     }
23 }
24 
25 void pushup(int u) {
26     if (tr[u].cnt) tr[u].len = tr[u].r + 1 - tr[u].l;
27     else if (tr[u].l == tr[u].r) tr[u].len = 0;
28     else tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
29 }
30 
31 void modify(int u, int l, int r, int c) {
32     if (tr[u].l >= l && tr[u].r <= r) {
33         tr[u].cnt += c;
34         pushup(u);
35     }
36     else {
37         int mid = tr[u].l + tr[u].r >> 1;
38         if (l <= mid) modify(u << 1, l, r, c);
39         if (r >= mid + 1) modify(u << 1 | 1, l, r, c);
40         pushup(u);
41     }
42 }
43 
44 int main() {
45     int n;
46     scanf("%d", &n);
47     for (int i = 0, j = 0; i < n; i++) {
48         int x1, y1, x2, y2;
49         scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
50         seg[j++] = {x1, y2, y1, 1};
51         seg[j++] = {x2, y2, y1, -1};
52     }
53     sort(seg, seg + n + n, [&](Seg &a, Seg &b) {
54         return a.x < b.x;
55     });
56     build(1, -1e4, 1e4);
57     int ret = 0;
58     modify(1, seg[0].y1, seg[0].y2 - 1, seg[0].c);
59     for (int i = 1; i < n + n; i++) {
60         ret += tr[1].len * (seg[i].x - seg[i - 1].x);
61         modify(1, seg[i].y1, seg[i].y2 - 1, seg[i].c);
62     }
63     printf("%d", ret);
64     
65     return 0;
66 }

 

参考资料

  AcWing 2032. 过度种植(春季每日一题2022):https://www.acwing.com/video/3878/

posted @ 2022-05-23 09:57  onlyblues  阅读(44)  评论(0编辑  收藏  举报
Web Analytics