题意
原题链接
求几个矩形的周长并
sol
遇到几何图形的**并,都可以使用扫描线思想来解决
观察易得,与x轴平行的边和与y轴平行的边相互独立,因此可以扫描两次,分别计算并累加
以与x轴平行的边为例:
假设有一条平行于x轴的直线从下到上扫描,每当遇到一条边时,若这条边是某个矩形的下边,则在直线上覆盖这条边,否则就在直线上清除掉对应下边的覆盖标记。我们设这是第\(i\)条边,在直线上有\(len_i\)单位长度被覆盖,则第\(i\)条边的贡献为\(|len_i - len_{i-1}|\),特别地,\(len_0 = 0\)
证明
当遇到一条边时,可能会有两种情况:
- 这条边是下边,则这条边中处于矩形并的部分不会计算贡献,而不位于矩形并中的部分的贡献为\(len_i - len_{i-1}\)
- 这条边是上边,则这条边处于除该矩形的所有矩形的并的部分不会计算贡献,而不位于该并的部分的贡献为\(len_{i-1} - len_i\)
综上,该边的贡献为\(|len_i - len_{i-1}|\)
因此,我们只需要处理以下三个操作:
- 在直线上的一个区间覆盖标记
- 在直线上删除一个区间的覆盖标记
- 查询被覆盖的单位长度
对于本题来说,由于数据较弱,因此这三个操作即便是\(O(n)\)的暴力也不会TLE,但对本题来说,仍可以使用线段树来优化到\(O(n\log n)\)
我们可以在线段树上记录两个变量:\(cnt\)表示该区间被覆盖的次数,\(val\)表示该区间被覆盖的单位长度。在UPDATE时,我们可以将边对应的值直接加到\(cnt\)中,若是矩形下边,该值就为\(1\),上边则为\(0\)。然后,若这个区间被整体覆盖过,即\(cnt \le 1\),则\(val\)就为区间长度,否则\(val\)为两个儿子的\(val\)之和
值得注意的是,由于单个点对于周长的贡献毫无意义,因此每个节点\(\{l, r\}\)都应维护区间\([l, r+1]\)的贡献。为了达成这个目的,在UPDATE时要将右端点\(-1\),在PUSHUP时再将其\(+1\)
同时,由于部分题目坐标为浮点数、坐标范围过大等原因,需要进行离散化(虽然本题不用)
最后,在查询时,只需要查询根节点的\(val\)就可以了
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_map>
using namespace std;
const int N = 10005;
struct Line{
int pos, a, b, tag;
bool operator< (const Line W) const{
if (pos != W.pos) return pos < W.pos;
return tag > W.tag;
}
}row[N], col[N]; //扫描线
struct Node{
int val, cnt;
}treer[N * 4], treec[N * 4]; //线段树
int n;
int hai2vr[N], hai2vc[N]; //将下标转为值
int hav2i(int *hai2v, int n, int x){ //将值转换为下标
return lower_bound(hai2v, hai2v + n, x) - hai2v;
}
int discr(Line *lines, int *hai2v){ //离散化
sort(hai2v, hai2v + n);
int i, j;
for (i = 1, j = 1; i < n; i ++ )
if (hai2v[i] != hai2v[i - 1]) hai2v[j ++ ] = hai2v[i];
for (i = 0; i < n; i ++ ){
Line &line = lines[i];
line.a = hav2i(hai2v, j, line.a);
line.b = hav2i(hai2v, j, line.b);
}
return j;
}
void pushup(Node *tree, int *hai2v, int u, int l, int r){
if (tree[u].cnt) tree[u].val = hai2v[r + 1] - hai2v[l];
else tree[u].val = tree[u << 1].val + tree[u << 1 | 1].val;
}
void update(Node *tree, int *hai2v, int u, int l, int r, int L, int R, int val){
if (L <= l && r <= R){
tree[u].cnt += val;
pushup(tree, hai2v, u, l, r);
return ;
}
int mid = l + r >> 1;
if (L <= mid) update(tree, hai2v, u << 1, l, mid, L, R, val);
if (R > mid) update(tree, hai2v, u << 1 | 1, mid + 1, r, L, R, val);
pushup(tree, hai2v, u, l, r);
}
int solve(Line *lines, Node *tree, int *hai2v){ //解题主函数
int nn = discr(lines, hai2v);
sort(lines, lines + n);
int ans = 0;
for (int i = 0, last = 0; i < n; i ++ ){
Line line = lines[i];
update(tree, hai2v, 1, 0, nn - 1, line.a, line.b - 1, line.tag);
ans += abs(tree[1].val - last);
last = tree[1].val;
}
return ans;
}
int main(){
scanf("%d", &n);
for (int i = 0; i < n; i ++ ){
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
row[i * 2] = {y1, x1, x2, 1};
row[i * 2 + 1] = {y2, x1, x2, -1};
col[i * 2] = {x1, y1, y2, 1};
col[i * 2 + 1] = {x2, y1, y2, -1};
hai2vr[i * 2] = x1, hai2vr[i * 2 + 1] = x2;
hai2vc[i * 2] = y1, hai2vc[i * 2 + 1] = y2;
}
n *= 2;
printf("%d\n", solve(row, treer, hai2vr) + solve(col, treec, hai2vc));
}
蒟蒻犯的若至错误
- 线段树区间的叶子节点处理成了单点导致无效贡献
- UPDATE操作中,被完全覆盖没有
return