矩形面积并
给定平面直角坐标系中
n
n
n 个四边均平行于坐标轴的矩形,求它们的面积并(即面积重叠的部分只计算一次)
通常
1
≤
n
≤
1
e
5
1 \leq n \leq 1e5
1≤n≤1e5
暴力
可以先求出每个矩形的面积求和,再枚举任意两个矩形的并,在答案中减去。
枚举矩形是
O
(
n
2
)
O(n^2)
O(n2) 的,无法胜任。代码很简单,就不放了
正解
(为了方便起见,我们参照数组,把左上角记为
(
1
,
1
)
(1,1)
(1,1) ,第
x
x
x 行
y
y
y 列记为
(
x
,
y
)
(x,y)
(x,y) 。这对答案没有影响)
让我们重新看待两个相交的矩形
如上图,我们可以把两个相交的矩形(如
(
a
)
(a)
(a) )看作三个(多个)无重叠的矩形(如
(
b
)
(b)
(b) )。这些矩形求和比较好做。我们可以认为矩形的左右两边列的差为矩形的高,这是好求的,接下来就是求宽的问题了
此时定义入边为矩形左边的那条边,出边为右边的那条边(如果你是横着分割的话,那么入边为上边的那条边,出边为下边那条边)。令
l
e
n
len
len 表示从左到右扫到当前矩形时的所有矩形的宽度之和,
l
e
n
len
len 的初值为
0
0
0 ,此时不难发现当扫到一条入边的时候
l
e
n
len
len 会增加,扫到出边时
l
e
n
len
len 会减少。于是用值域线段树维护当前的宽的和,统计答案时用这个值乘上高即可。具体而言,扫到区间
(
l
,
r
)
(l,r)
(l,r) 时将线段树上
(
l
,
r
)
(l,r)
(l,r) 的值增加(或减少)
1
1
1 ,统计时只用看哪些区域有值就可以了。详见代码(洛谷模板题)
#include <bits/stdc++.h>
using namespace std;
#define sort stable_sort
#define rep(i,n) for(int i = 1;i <= n;i++)
#define int long long
#define swap(x,y) (x ^= y ^= x ^= y)
#define debug cout<<"Help!\n"
const int N = 1e6 + 5;
int n,tot,X1,X2,Y1,Y2,ans;
vector <int> v;
struct scanline{
int l,r,h,in_out;
inline bool operator < (const scanline & b) const {
return h < b.h;
}
}line[N];
struct Seg{
#define ls(u) (u << 1)
#define rs(u) (u << 1 | 1)
struct Tree{
int len,cnt;//区间的总宽度,目前覆盖该区间的矩形个数
}t[N<<2];
inline void pushup(int u,int l,int r){
if(t[u].cnt) t[u].len = v[r] - v[l-1];
else t[u].len = t[ls(u)].len + t[rs(u)].len;
}
void update(int u,int L,int R,int l,int r,int x){
if(l <= L && R <= r){
t[u].cnt += x;
pushup(u,L,R);
return;
}
int mid = (L + R) >> 1;
if(l <= mid) update(ls(u),L,mid,l,r,x);
if(r > mid) update(rs(u),mid + 1,R,l,r,x);
pushup(u,L,R);
}
}seg;
inline int find(int x){
return lower_bound(v.begin(),v.end(),x) - v.begin() + 1;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
rep(i,n){
cin>>X1>>Y1>>X2>>Y2;
//小心一下。作者写的时候脑子抽了在下面一句把 x1 y1,x2 y2 分别写反了,虽然不影响正确性,但是在写的时候还是注意逻辑一惯性比较好
line[++tot] = {Y1,Y2,X1,+1},line[++tot] = {Y1,Y2,X2,-1};//如果是入边赋 +1 的权值,出边赋 -1 的权值
v.emplace_back(Y1),v.emplace_back(Y2);//这题需要离散化
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
sort(line + 1,line + tot + 1);//按照区间的位置(即理应当的 y 值)排序方便扫描
rep(i,tot){
ans += seg.t[1].len * (line[i].h - line[i-1].h);//目前的宽度之和 * 高
seg.update(1,1,v.size(),find(line[i].l),find(line[i].r) - 1,line[i].in_out);//更新宽度
}
cout<<ans;
return 0;
}
矩形周长并
给定平面直角坐标系中 n n n 个四边均平行于坐标轴的矩形,求它们的周长(所有矩形合并后的边长称为周长)
解法
其实与矩形面积并类似,但是可以再扫描竖线的时候顺便计算横线
有点复杂,但其实就是加上统计区间内有多少独立的横线即可。在这里不好讲清楚,看代码里的注释吧
以下是一道 模板题 的代码
#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i = 1;i <= n;i++)
const int N = 2e4 + 5;
struct scanline{
int l,r,h,in_out;
}line[N];
inline bool cmp(scanline a,scanline b){
if(a.h == b.h) return a.in_out > b.in_out;
return a.h < b.h;
}
int n,tot,minn = 1e4,maxx = -1e4,X1,Y1,X2,Y2,ans,lst;
struct node{
bool l,r;//该区间左右端点是否被覆盖
int num,tag,length;//该区间横线数量,该节点是否有效,该区间的有效长度
}t[N<<2];
inline void pushup(int u,int l,int r){
if(t[u].tag){
t[u].l = t[u].r = true;
t[u].num = 1,t[u].length = r - l + 1;
return;
}
if(l == r){
t[u].num = t[u].length = t[u].l = t[u].r = false;
return;
}
t[u].l = t[u<<1].l,t[u].r = t[u<<1|1].r;
t[u].length = t[u<<1].length + t[u<<1|1].length,t[u].num = t[u<<1].num + t[u<<1|1].num;
t[u].num -= t[u<<1].r & t[u<<1|1].l;
}
void update(int u,int l,int r,int L,int R,int add){
if(l <= L && R <= r){
t[u].tag += add;
pushup(u,L,R);
return;
}
int mid = (L + R) >> 1;
if(l <= mid) update(u << 1,l,r,L,mid,add);
if(r > mid) update(u << 1 | 1,l,r,mid + 1,R,add);
pushup(u,L,R);
}
template <typename T>
inline void read(T & x){
x = 0;
T f = 1;
char c;
for(c = getchar();!isdigit(c);c = getchar()) if(c == '-') f = -1;
for(;isdigit(c);c = getchar()) x = (x << 3) + (x << 1) + (c ^ 48);
x *= f;
}
template <typename T>
inline void write(T x){
if(x / 10) write(x / 10);
putchar((x % 10 + '0'));
}
int main(){
read(n);
rep(i,n){
read(X1),read(Y1),read(X2),read(Y2);
minn = min(minn,X1),maxx = max(maxx,X2);
line[++tot] = (scanline){X1,X2,Y1,1},line[++tot] = (scanline){X1,X2,Y2,-1};//记录入边出边
}
sort(line + 1,line + tot + 1,cmp);
rep(i,tot){
if(line[i].l < line[i].r) update(1,line[i].l,line[i].r - 1,minn,maxx - 1,line[i].in_out);//如果该矩形合法
ans += t[1].num * 2 * (line[i+1].h - line[i].h) + abs(lst - t[1].length);//重点:目前的横线数量 * 横线长度 + 竖线长度的变化
lst = t[1].length;
}
write(ans);
return 0;
}