概述
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1828
说完了矩形面积,矩形周长的方法自然是类似的,但是周长的计算却更复杂些,看这张图:
周长可以分成两部分计算,横线和竖线,如图将所有彩色的横线加起来就是横向的所有长度了
然后可以采用竖直方向的扫描线将竖线的所有长度求出来
那么怎么计算横线的长度呢?
横线的长度 = 【现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值】
想想为什么要加绝对值(提示:下边--删除,上边--插入)
这样用自下而上和从左往右的两次扫描即可得到答案。
但是,这样的方法显得有些笨,有没有更高端的方法呢?
再看一张图:
看出什么了吗?这张图在上面那张图的基础上多了几条竖线。
我的意思是说,我们可以只做一次自下而上的扫描就把横线竖线都算出来!
竖线的算法和上面说的方法一样:【现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值】
竖线要怎么计算?
首先我们现在改一下线段树保存的属性,我们用如下信息记录线段树的节点:
1. l , r : 该节点代表的线段的左右端点坐标
2.len : 这个区间被覆盖的长度(即计算时的有效长度)
3.s : 表示这个区间被覆盖了几次
4. lc , rc : 标记这个节点的左右两个端点是否被覆盖(0表示没有,1表示有)
5.num :这个区间有多少条线段(这个区间被多少条线段覆盖)
这里的num涉及到竖线的计算,故解释一下,举几个例子:
若区间[0,10]被[1,2][4,5]覆盖,则num = 2
若区间[0,10]被[1,3][4,5]覆盖,则num = 1(两区间刚好连在一起)
若区间[0,10]被[1,5][2,6]覆盖,则num = 1(两区间连起来还是一段)
然后就可以计算竖线了:
竖线的长度 = 【下一条即将被扫到的横线的高度 - 现在扫到的横线的高度】*2*num
乘2是因为每条线段有两个端点;
看上图中棕色线段的竖线有4条,因为棕色的横线由2条线段组成
白色线段的竖线只有2条,因为白色的横线由1条线段组成(虽然这1条线段是由许多线段组合而成,但它依旧只算1条线段)
这样,依旧只扫一次就可以算出周长。
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<set>
#include<stack>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
int n;
const int INF=5e3+5;
const int Max=2e4+2000;
struct node{
int from;
int to;
int h;
int d;
}g[2*INF];
struct Node{
int right;
int left;
int sum;//覆盖区间的长度
bool ll;//标记这个节点的左右两个端点是否被覆盖
bool rr;//(0表示没有,1表示有)
int mark;//标记区间是否被覆盖
int num;//这个区间有多少条线段(这个区间被多少条线段覆盖)
}f[4*Max];
bool cmp(node p1,node p2)
{
return p1.h <p2.h ;
}
void build(int ans,int l,int r){
f[ans].left =l;f[ans].right =r;
f[ans].mark =f[ans].num =f[ans].sum =0;
f[ans].ll =f[ans].rr =0;
if(l==r)return ;
int mid=(l+r)>>1;
build(ans<<1,l,mid);
build(ans<<1|1,mid+1,r);
}
void pushup(int ans){
if(f[ans].mark ){//整个区间被覆盖
f[ans].sum =f[ans].right -f[ans].left +1;
f[ans].rr =1 ;
f[ans].ll =1 ;
f[ans].num =2;
}
else if(f[ans].left ==f[ans].right ){
f[ans].sum =0;
f[ans].num =0;
f[ans].ll =f[ans].rr =0;
}
else{
f[ans].sum =f[ans<<1].sum +f[ans<<1|1].sum ;
f[ans].num =f[ans<<1].num +f[ans<<1|1].num ;
f[ans].ll =f[ans<<1].ll ;//和左儿子共左端点
f[ans].rr =f[ans<<1|1].rr;//和右儿子共右端点
if(f[ans<<1].rr&&f[ans<<1|1].ll ){//此时两段合为一段
f[ans].num -=2;
}
}
}
void update(int ans,int l,int r,int x)
{
if(f[ans].left>=l&&f[ans].right <=r)
{
f[ans].mark +=x;
pushup(ans);
return;
}
int mid=(f[ans].left +f[ans].right )>>1;
if(l<=mid){
update(ans<<1,l,r,x);
}
if(r>mid){
update(ans<<1|1,l,r,x);
}
pushup(ans);
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
if(n==0)
{
printf("0n");continue;
}
int i,j;
int mx=-10000,mi=10000;
for(i=1,j=0;i<=n;i++)
{
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
mx=max(mx,x2);
mi=min(mi,x1);
g[++j].d =1;g[j].from =x1;
g[j].to =x2;g[j].h =y1;
g[++j].d =-1;g[j].from =x1;
g[j].to =x2;g[j].h =y2;
}
sort(g+1,g+1+j,cmp);//数据不大,不用离散化
build(1,mi,mx-1);//n个x点之间有(n-1)个区间
int sum=0;
int last=0;
for(i=1;i<=j;i++)//注意这里和计算面积不一样,要取到最上面的那条边
{
if(g[i].from <g[i].to )update(1,g[i].from ,g[i].to-1 ,g[i].d );
sum+=(g[i+1].h-g[i].h
)*(f[1].num );//竖线的长度 = 【下一条即将被扫到的横线的高度 - 现在扫到的横线的高度】*num
sum+=abs(last-f[1].sum );//横线的长度 = 【现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值】
last=f[1].sum ;//每次都要更新上一次总区间覆盖的长度
}
printf("%dn",sum);
}
return 0;
}
最后
以上就是无限耳机为你收集整理的HDU - 1828 -Picture (线段树+扫描线+求矩形围成图形周长)的全部内容,希望文章能够帮你解决HDU - 1828 -Picture (线段树+扫描线+求矩形围成图形周长)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复