题目:
解题思路:
题目大意:给你n个边平行与坐标轴的矩形,矩形可能有重叠部分,问你所有矩形构成的图形的轮廓线多长(即外周长+内部中空的周长)
错误想法:做了之前的求矩形面积和的问题,那么自然会想到每一根扫描线都用线段树维护了有效的长度,那么每一次都加上那个长度,先从左往右跑一边,再从上往下跑一遍,答案不就出来了?这真的对吗?答案是:错误的!题目只要求出轮廓线长度,如果那样算,结果比答案大很多,因为算了很多不必要的长度。
正确做法:
先考虑横边:
观察下图中扫描线扫过的横边,会发现它好像有一点规律,总结一下就是:横边总长度=
∑
2
×
被
截
得
的
线
段
条
数
×
扫
过
的
宽
度
\sum2\times被截得的线段条数\times扫过的宽度
∑2×被截得的线段条数×扫过的宽度。被截得的线段条数,即非连续线段。
在考虑竖边:
事情就更加 恶心 有趣了,你会发现竖边总长度
=
∑
∣
上
次
截
得
的
总
长
−
−
现
在
截
得
的
总
长
∣
=\sum|上次截得的总长-−现在截得的总长∣
=∑∣上次截得的总长−−现在截得的总长∣。
所以和面积并比起来,周长并中的线段树还要维护线段的条数。
注意点:
1.如果出现两条 x 坐标相等的扫描线,应该先处理入边,然后处理出边,因为这条公共边只需要处理一次!先处理入边,新的扫描线得长度不会变,那么答案不会增加,在处理出边时,新扫描线长度减少,答案增加相应长度,这样就计算了一遍;如果先处理出边,那么新的扫描线减少了,答案增加相应长度,再处理入边时,新扫描线的长度又增加了,答案又增加了一遍长度,重复计算了!所以,扫描线的排序有所不同!
2.当计算线段条数时,应该考虑中间合并的情况,所以要增加两个变量 lc,rc ,代表该区间的左端点是否覆盖,右端点是否覆盖。当合并时,如果左子区间的右端点被覆盖,右子区间的左端点被覆盖,那么父节点的线段条数应该减1。
3.别忘记最后答案加上最后一条扫描线的长度。
代码:
#include <bits/stdc++.h>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define pb push_back
using namespace std;
const int MAXN = 1e4 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
int n;
ll ans;
struct line{
int x, y1, y2;
int state;
bool operator<(line b) const {
if(x==b.x) return state>b.state;//重合时,先加边,再减边
return x<b.x;
}
};
int ls[MAXN];//去重排序后的y坐标
int alls[MAXN];//所有y坐标
int lscnt;
int ask(int x){
return lower_bound(ls+1, ls+1+lscnt, x) - ls;
}
struct node1{
int l, r;//线段的编号
int len;//该区间覆盖线段的长度
int cover;//被全部覆盖的次数
bool lc,rc;//左端点是否被覆盖 右端点是否被覆盖
int cnt;//区间内线段的个数
}tree[MAXN << 3];
void pushup(int rt){
if(tree[rt].cover)
{
tree[rt].len=ls[tree[rt].r+1]-ls[tree[rt].l];
tree[rt].lc=true;
tree[rt].rc=true;
tree[rt].cnt=1;
}
else
{
tree[rt].len=tree[rt<<1].len+tree[rt<<1|1].len;
tree[rt].lc=tree[rt<<1].lc;
tree[rt].rc=tree[rt<<1|1].rc;
tree[rt].cnt=tree[rt<<1].cnt+tree[rt<<1|1].cnt;
if(tree[rt<<1].rc && tree[rt<<1|1].lc)//合并时中间连续的
tree[rt].cnt-=1;
}
}
void build(int l, int r, int rt){
tree[rt].l=l;
tree[rt].r=r;
tree[rt].len=tree[rt].cover=tree[rt].cnt=0;
tree[rt].lc=tree[rt].rc=false;
if(l == r) return ;
int m = (l + r) >> 1;
build(l, m, rt << 1);
build(m+1, r, rt << 1 | 1);
}
void update(int L, int R, int state, int rt){
if(L <= tree[rt].l && tree[rt].r <= R){
tree[rt].cover+=state;
pushup(rt);
return ;
}
int m = (tree[rt].l + tree[rt].r) >> 1;
if(L <= m)
update(L, R, state, rt << 1);
if(m < R)
update(L, R, state, rt << 1 | 1);
pushup(rt);
}
void solve(){
vector<line> v;
int cnt=0;
for(int i = 1; i <= n; i++){
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
v.pb(line{x1, y1, y2, 1});
v.pb(line{x2, y1, y2, -1});
alls[++cnt] = y1;
alls[++cnt] = y2;
}
sort(alls+1, alls+1+cnt);
lscnt = 0;
alls[0] = -inf;
for(int i = 1; i <= cnt; i++)
if(alls[i] != alls[i-1])
ls[++lscnt] = alls[i];
sort(v.begin(), v.end());
build(1, lscnt-1, 1);
ans = 0;
int pre=0;
for(int i = 0; i < 2*n-1; i++){
update(ask(v[i].y1), ask(v[i].y2)-1, v[i].state, 1);
ans+=abs(tree[1].len-pre);
ans+=(v[i+1].x-v[i].x)*2*tree[1].cnt;
pre=tree[1].len;
}
ans+=v[2*n-1].y2-v[2*n-1].y1;//最后一条边的长度
}
int main()
{
qc;
while(cin>>n)
{
solve();
printf("%lld\n",ans);
memset(alls,0,sizeof(alls));
memset(ls,0,sizeof(ls));
memset(tree,0,sizeof(tree));
}
return 0;
}
碎碎念:
这规律,妙啊!