题解 P1884 【[USACO12FEB]过度种植(银)Overplanting …】
扫描线板子题,可以去搜博客看一下。这里搬运一下以前口胡的。
首先将y坐标离散化,但是要保留原坐标。
我们用一根竖直的线从左向右依次扫描,每当与矩形的竖直边重合时就累加一次面积,相当于把并集图形分割为一个一个小的矩形。
具体来说,建立数组
对所有竖线先按x排序,有些写法里对竖线排序时如果x相等会按c排序,但是容易发现这是毫无影响的。
对于每条边,我们把
(为什么是左闭右开:我们把点转化为线段来求覆盖次数,点的数量是比线段的数量多1个的,比如第一条线段里
接着统计答案,下一个矩形的宽就是相邻两根竖边的x坐标差,而长度就是d数组里至少被覆盖一次的坐标数。
(图中:
d数组涉及了区间修改和区间查询,可以使用线段树来维护。
由于只需要查询
#include <bits/stdc++.h>
#define MAX (2000 + 7)
using namespace std;
struct Node { int x, y0, y1, c; } a[MAX];
int cmp(Node a, Node b) { return a.x < b.x; }
unordered_map <int, int> H;
int N, M, qy[MAX];
long long ans;
struct Segtree
{
#define i0 (i << 1)
#define i1 (i << 1 | 1)
int L[MAX << 2], R[MAX << 2], v[MAX << 2], len[MAX << 2];
void init(int i, int l, int r)
{
L[i] = l, R[i] = r, v[i] = len[i] = 0;
if (l != r) {
int mid = l + r >> 1;
init(i0, l, mid), init(i1, mid+1, r);
}
}
void add(int i, int l, int r, int c)
{
if (r < L[i] || R[i] < l) return;
if (l <= L[i] && R[i] <= r)
v[i] += c;
else add(i0, l, r, c), add(i1, l, r, c);
if (v[i]) len[i] = qy[R[i]+1] - qy[L[i]];//完全覆盖时的长度应该是离散化前的y坐标(实际长度)。
else len[i] = len[i0] + len[i1];
}
} Seg;
int main()
{
scanf("%d", &N), H.clear();
for (int i = 1, x0, y0, x1, y1; i <= N; i++)
{
scanf("%d%d%d%d", &x0, &y1, &x1, &y0);//注意是左上和右下
a[i] = Node{x0, y0, y1, 1};
a[i+N] = Node{x1, y0, y1, -1};
qy[++M] = y0, qy[++M] = y1;
}
sort(qy+1, qy + M+1), M = unique(qy+1, qy + M+1) - qy - 1;
for (int i = 1; i <= M; i++)
H[qy[i]] = i;//离散化
Seg.init(1, 1, (N <<= 1));
sort(a + 1, a + N + 1, cmp);
for (int i = 1; i <= N; i++)
{
Seg.add(1, H[a[i].y0], H[a[i].y1] - 1, a[i].c);
ans += (long long)Seg.len[1] * (a[i+1].x - a[i].x);
} printf("%lld\n", ans);
}
另外,本题N规模为1000,最多2000个不同的坐标,线段树开8000是足够的,但是上面的代码如果开了O2就会WA。必须要开更大的空间才能过,如果有知道原因的dalao欢迎来踩。