HDU 1828 Picture(线段树:扫描线)
http://acm.hdu.edu.cn/showproblem.php?pid=1828
题意:
给你n个边平行与坐标轴的矩形,矩形可能重叠,问你所有矩形构成的图形的轮廓线多长(即外周长+内部中空的周长)?
分析:
本题和HDU1542类似,详解请见: http://blog.csdn.net/u013480600/article/details/22548393
下面继续拿HDU1542的两个矩形为例分析:
不过由于本题求得是周长,所以将所有上下位边按y从小到大排序之后:
首先我们读入第一条扫描线(下位边+1),这条扫描线的长度直接加入ans结果中.
现在线段树整体就有两条竖向边了(平行于y轴的),所以这两条竖向边从第一条扫描线到第二条扫描线之间是会增涨的,所以总周长ans+=2*(第二条扫描线的高h2-第一条扫描线的高h1).
然后我们读入第二条扫描线(下位边+1):
由于我们更新完这条扫描线后,sum[1]覆盖的范围扩大了5,所以ans先加5.其次这个时候,我们的竖向边由两条变成了3条,所以ans+=3*(扫描线3的高度h3-扫描线2的高度h2).
然后我们读入第三条扫描线(上位边-1):
由于我们读入该扫描线后sum[1]的覆盖值少了5,但是此时我们看图知道其实这个矩形的另一条上边了.所以我们不能用ans+=sum[1]了,我要执行ans+=abs(last-sum[1]),其中last是上一轮读入扫描线后sum[1]的值.(这里要注意一下,结合我们读入前两条扫描线的情况分析下,其实每次ans首先加的值都应该是abs(last-sum[1]),然后才是竖向边增加的部分.)
后面基本类似,可以分析出来.
根据上面的分析现在来分析线段树应该维护的信息.其中这里的线段树与HDU1542的线段树一样,其叶节点维护的是一个一个的区间,而不是x轴坐标点,要记住,线段树需要维护的信息有:
1. cnt(根本信息,只能通过update修改): 表示当前节点控制的区间中 下位边数目-上位边数目的结果
如果cnt==-1,那么表示其儿子节点的cnt不一致
2. sum(辅助查询信息,可以通过cnt的值改变而被改变): 表示节点控制的区间中被覆盖的边的总长度
3. pre: 表示节点控制的区间中被被覆盖的边的前缀长度
4. suf: 表示节点控制的区间中被被覆盖的边的后缀长度
5. numseg:表示节点控制的区间中竖向边的个数
下面分析线段树的基本操作:
1. PushDown(i,l,r):如果cnt!=-1的话,那么就修改子树的各类信息.
2. PushUp(i,l,r):根据儿子节点的各类信息修改父节点的各类信息,注意如果左儿子的后缀和右儿子的前缀都不为0,那么父节点的numsge需要-2.
3. build函数需要初始化cnt,numseg,pre,suf,sum 都为0,所以可以直接用memset初始化即可.不用build递归了.
4. update类似于:HDU1542的update
下面用另一种方式来写代码:要做点小修改,其中pre和suf不再是表示前缀和后缀长度了,而是用来表示是否存在前缀和后缀.另外各个树节点的cnt值不上传也不下传,仅仅保存在自己这个节点就可以.所以不需要PushDown函数了且cnt的值只能>=0了,不存在-1的结果.
但是还需要PushUp函数把sum,pre,suf,numseg等信息传递给父节点.所以PushUp实现:
如果当前节点的cnt值不为0,那么当前节点控制的区域肯定被完全覆盖了,sum=r-l+1,numseg=2,pre=suf=1.
否则cnt=0的话,此时如果l==r,即该节点是叶节点,那么sum=numseg=pre=suf=0
如果此时l不等于r,即该节点不是叶节点, 且cnt此时是0,所以本节点的信息要从子节点获取了.
update(ql,qr,i,l,r):如果[ql,qr]区间包括了[l,r]区间,那么直接更新当前节点信息,否则递归处理左右儿子,然后在PushUp即可.记住这里不用PushDown,因为cnt(根本信息)不传递.
这种方式需要仔细体会,cnt的值固定在某个节点,不上传也不下传.
注意:由于区间[l,r]被分成了区间[l,m],和区间[m+1,r],我们要保证这两个区间内都应该有值,否则如果m=r的话,就可能出现无限递归.比如:
void update(int ql,int qr,int v,int i,int l,int r)
{
if(ql<=l&&r<=qr)
{
cnt[i]+=v;
PushUp(i,l,r);
return ;
}
int m=(l+r)/2;
if(ql<=m) update(ql,qr,v,lson);
if(m<qr)update(ql,qr,v,rson);
PushUp(i,l,r);
}
上面这个函数如果update(-1,-1,0,9,-1,0)的时候,此时的l=-1,r=0,m=0,
且ql=-1,qr=-1,直接就产生了无限递归,看看是不是这样.我们如何保证不产生无限递归呢?只需要保证两个子区间都不空就可以,那么每次update的区间都会减少.由于我们是划分[l,m]和[m+1,r]的区间,所以这个m一定要比r小,m不能等于r,所以m应该是靠近l划分的.
下面有3种划分方式:
m=(l+r)/2 :该方式使得m朝0的方向走,如果r=0的话,m就靠近r
m=l+(r-l)/2:该方式是使得m靠近l划分
m=(l+r)>>1:该方式也是使得m靠近l划分.
综上所述,m的求法一定要小心.如果l和r非负的时候上面3种方式都可以,但是如果有负数,就要用后面两种划分方式.
AC代码:0ms
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MAXN=22222; #define lson i*2,l,m #define rson i*2+1,m+1,r int cnt[MAXN*4],suf[MAXN*4],pre[MAXN*4],numseg[MAXN*4],sum[MAXN*4]; struct node { int l,r,h,d; node(){} node(int a,int b,int c,int d):l(a),r(b),h(c),d(d){} bool operator < (const node & b)const { if (h == b.h) return d > b.d;//这句话不写也AC,但是还是写上保险,对于本题来说写不写没区别 return h<b.h; } }nodes[MAXN]; void PushUp(int i,int l,int r) { if(cnt[i]) { numseg[i]=2; pre[i]=suf[i]=1; sum[i]=r-l+1; } else if(l==r) numseg[i]=pre[i]=suf[i]=sum[i]=0; else { numseg[i]=numseg[i*2]+numseg[i*2+1]; if(suf[i*2] && pre[i*2+1]) numseg[i]-=2; sum[i]=sum[i*2]+sum[i*2+1]; pre[i]=pre[i*2]; suf[i]=suf[i*2+1]; } } void update(int ql,int qr,int v,int i,int l,int r) { if(ql<=l&&r<=qr) { cnt[i]+=v; PushUp(i,l,r); return ; } int m=l+(r-l)/2;//这里一定小心,如果是m=(l+r)/2,会无限递归,栈溢出,如ql=qr=-1且l=-1,r=0的时候 if(ql<=m) update(ql,qr,v,lson); if(m<qr) update(ql,qr,v,rson); PushUp(i,l,r); } int main() { int t; while(scanf("%d",&t)==1) { int m=0; int lbd=10000,rbd=-10000; for(int i=1;i<=t;i++) { int x1,y1,x2,y2; scanf("%d%d%d%d",&x1,&y1,&x2,&y2); lbd=min(lbd,x1); rbd=max(rbd,x2); nodes[++m]= node(x1,x2,y1,1); nodes[++m]= node(x1,x2,y2,-1); } sort(nodes+1,nodes+m+1); int ans=0,last=0;; for(int i=1;i<=m;i++) { int ql=nodes[i].l; int qr=nodes[i].r-1; if(ql<=qr)update(ql,qr,nodes[i].d,1,lbd,rbd-1); ans += abs(last-sum[1]); last=sum[1]; if(i<m) ans+= numseg[1]*(nodes[i+1].h-nodes[i].h); } printf("%d\n",ans); } }