题目链接:Picture - POJ 1177 - Virtual Judge
题意:墙上贴着许多形状相同的海报、照片。它们的边都是水平和垂直的。每个矩形图片可能部分或全部的覆盖了其他图片。所有矩形合并后的边长称为周长。编写一个程序计算周长
分析:做这道题目之前需要对扫描线离散化基本知识有一定的了解,如果还有不了解扫描线的小伙伴可以看下我之前的一篇介绍扫描线的博客,下面
附上博客地址:离散化扫描线_AC__dream的博客-CSDN博客
其实求合并后矩形周长和求合并后矩形面积是差不多的,先来给个例子进行介绍一下求法:
这是原图
这是合并后图形的轮廓
我们依旧是先把所有边存起来,求合并后矩形面积时我们可以选择处理与x轴平行的边也可以选择与y轴平行的边,而求合并后矩形周长时我们需要对这两部分分开处理,最后处理一下加和,因为合并后矩形的周长由两部分组成,一部分是平行于x轴的边,另一部分是平行于y轴的边。这两部分的处理方法基本上是一模一样的,只不过一个是按照x存边,另一个是按照y存边,我们下面就只介绍一下平行于合并矩形后形成的图形平行于y轴的边的和怎样求。
我们先把所有平行于y轴的边存起来,按照x递增顺序一共有(1,3,6,1),(3,2,4,1),(3,5,7,1),(5,3,6,-1),(6,1,6,1),(7,5,7,-1),(8,2,4,-1),(10,1,6,-1)8条边,依次考虑每一条边,修改完之后的扫描线长度依次是3,4,5,6,5,0,而我们通过这个图容易发现依次修改每条边后,周长依次增加3,1,1,1,1,5,有没有突然发现什么规律?就是我们每次修改一次边,周长会增加修改后和修改前扫描线的长度差的绝对值,也就是说周长的增加值正好是扫描线有效长度的变化量(注意是正值),那这样我们就很容易求得与y轴平行的边的周长了,同理可求得与x轴平行的边的周长和,相加就是结果了。
有个细节大家必须要考虑到:就是当我们同一x有出边和入边时,一定要先对入边进行修改,否则会出错,举个例子:
对于同一x=6时,如果先对入边(6,2,7,1)进行修改,后对出边(6,3,6,-1)进行修改
依次考虑每一条边,修改完之后的扫描线长度依次是3,5,5,0,这样计算的平行于y轴的周长为|3-0|+|5-3|+|5-5|+|0-5|=3+2+0+5=10,这是正确的结果
而如果先对出边(6,3,6,-1)进行修改,后对入边(6,2,7,1)进行修改
依次考虑每一条边,修改完之后的扫描线长度依次是3,0,5,0,这样计算的平行于y轴的周长为|3-0|+|0-3|+|5-0|+|0-5|=3+3+5+5=16,结果显然是错误的,因为这样是会计算进去位于合并后图形轮廓内部的周长的
所以切记,对于同一x或y,一定要先修改入边后修改出边,否则会重复计算导致结果错误
下面是代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=1e5+10;
int l[N],r[N],cnt[N],len[N];
int x1[N],y1[N],x2[N],y2[N];
vector<int>alls;
int find(int x)
{
return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1;
}
struct edge{
int x,yn,yx,k;
}p[N];
bool cmp(edge a,edge b)
{
if(a.x!=b.x)
return a.x<b.x;
return a.k>b.k;
}
void pushup(int id)
{
if(cnt[id]) len[id]=alls[r[id]]-alls[l[id]-1];//点区间[l[id],r[id]]实际表示的是线段区间[alls[l[id]-1],alls[r[id]]]
else if(l[id]==r[id]) len[id]=0;
else len[id]=len[id<<1]+len[id<<1|1];
}
void build(int id,int L,int R)
{
l[id]=L;r[id]=R;cnt[id]=len[id]=0;
if(L==R) return ;
int mid=L+R>>1;
build(id<<1,L,mid);
build(id<<1|1,mid+1,R);
}
void update_interval(int id,int L,int R,int k)
{
if(l[id]>=L&&r[id]<=R)//当前区间完全在目标区间中
{
cnt[id]+=k;
pushup(id);
return ;
}
int mid=l[id]+r[id]>>1;
if(mid>=L) update_interval(id<<1,L,R,k);
if(mid+1<=R) update_interval(id<<1|1,L,R,k);
pushup(id);
}
int main()
{
int n;
cin>>n;
int cnt=0;//从此处开始计算多边形与y轴平行的边的边长和
for(int i=1;i<=n;i++)
{
scanf("%d%d%d%d",&x1[i],&y1[i],&x2[i],&y2[i]);
alls.push_back(y1[i]);alls.push_back(y2[i]);;
p[++cnt]={x1[i],y1[i],y2[i],1};
p[++cnt]={x2[i],y1[i],y2[i],-1};
}
sort(p+1,p+cnt+1,cmp);
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
for(int i=1;i<=cnt;i++)
{
p[i].yn=find(p[i].yn);
p[i].yx=find(p[i].yx);
}
build(1,1,alls.size());
long long ans=0;
int t=0;
for(int i=1;i<=cnt;i++)
{
update_interval(1,p[i].yn,p[i].yx-1,p[i].k);
ans+=abs(len[1]-t);
t=len[1];
}
//从此处开始计算多边形与x轴平行的边的边长和
cnt=0;
alls.clear();//注意清空数组
for(int i=1;i<=n;i++)
{
alls.push_back(x1[i]);alls.push_back(x2[i]);;
p[++cnt]={y1[i],x1[i],x2[i],1};
p[++cnt]={y2[i],x1[i],x2[i],-1};
}
sort(p+1,p+cnt+1,cmp);
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
for(int i=1;i<=cnt;i++)
{
p[i].yn=find(p[i].yn);
p[i].yx=find(p[i].yx);
}
build(1,1,alls.size());
t=0;
for(int i=1;i<=cnt;i++)
{
update_interval(1,p[i].yn,p[i].yx-1,p[i].k);
ans+=abs(len[1]-t);
t=len[1];
}
printf("%lld",ans);
return 0;
}