做这道题之前,建议先做POJ 1151 Atlantis,经典的扫描线求矩阵的面积并
参考连接:
http://www.cnblogs.com/scau20110726/archive/2013/04/13/3018702.html
线段树辅助——扫描线法计算矩形周长并(轮廓线):
http://www.cnblogs.com/scau20110726/archive/2013/04/13/3018687.html
http://blog.csdn.net/ophunter/article/details/9129557
题意:求几个矩形并后的周长
思路:这和求矩形并的面积差不多,也是要用到扫描线,一根一根地扫描。只不过这里比求面积的要麻烦点。
离散化就不用说了,具体说下如何求周长吧。
首先将横线的信息存起来,按照y从小到大排序,当y相同时,矩阵的下边界排在前面,上边界排在后面,具体原因后面会有说明。
然后一条一条的开始插入,计算周长分为两种:
1.计算垂直方向的周长时,首先我们要知道整个区间被分成了多少段,即该区间被多少条线段覆盖,设为m,
那么垂直方向的增量即为2*m*(y2-y1),(y2-y1)即为当前扫描线的纵坐标和前一次纵坐标的差值。
2.计算水平方向的周长时,统计当前区间的总长度,同时记录上一个状态的时候区间总长度,那么这2次区间总长度之差的绝对值
就是插入当前扫描线后,总区间内水平方向的增量。
不过这里有一点要注意的是:会有重边的出现。
当纵坐标一样时,为了防止矩阵A的上边界和矩阵B的下边界重合时,实际上增量为0。
但如果先加入矩阵A的上边界l1,即先删除,当前区间总长度为绿色的线条1,现在变为蓝色的线条2,增量为某值a;
再加入矩阵B的下边界l2,上一次区间总长度为线条2,现在变为紫色的线条3,增量又为a,这样导致多计算了2*a。
所以正确的是应该先扫描矩阵B的下边界(tp=1),再扫描矩阵A的上边界(tp=-1),即y相同时,tp=1的排在前面。即先插入,再删除。
这样先插入l2,区间总长度不变,再插入l1,区间总长度仍不变,这样增量就为0.
不过测试的数据里没有出现重边的情况,所以AC的方法并不一定是正确的。
我线段树建立的叶子节点是区间(a,a+1),而不是点,这样是为了方便求区间长度。
更新的时候,是直接更新到叶子节点的。
本想在更新时,用lazy标记,这样不必更新到叶子节点,但是样例一直不过。
后来调试时候,发现当更新某一个节点时,该节点cnt=1,但是父节点的cnt仍是0。
这样如果某次更新正好更新到父节点时,cnt为0,但实际上父节点中所包含的区间中有部分cnt=1,这样就导致更新的时候会不正确。
因此正确的更新还是要更新到叶子节点。
最后还要说明一下的是:POJ 上面只有一组数据,而HDU 上面有多组数据。
附上代码:
#include <iostream> #include <stdio.h> #include <algorithm> #include <string.h> #include <math.h> #define lson rt<<1,L,mid #define rson rt<<1|1,mid,R using namespace std; const int maxn=10005; int n,cnt=0; //n为矩形的个数,cnt为离散后的点x的个数 int hashval[maxn]; //x坐标对应的离散的值 int xval[maxn]; //存储所有的x坐标的值 int idx=0; //xval存储的数的个数 struct Line { int l,r,y; //l:左端点 r:右端点 y:纵坐标 int tp; //标记,1为矩形的下边界,-1为矩形的上边界 bool operator<(const Line tmp)const { /* 当纵坐标一样时,矩阵的下边界(tp=1)排在前面,矩阵的上边界(tp=-1)排在后面 */ if(y==tmp.y) return tp>tmp.tp; return y<tmp.y; } } line[maxn]; int lnum=0; struct Node { int lp,rp; //标记左右端点是否被线条覆盖,1为是,0为否,用于在pushUp时,统计父亲的num值 int cnt; //表示这个区间被覆盖的次数 int len; //这个区间被覆盖的长度 int num; //该区间被多少条线段覆盖 } tree[maxn<<2]; //二分搜索离散后的值 int binarySearch(int m) { int l=1,r=cnt,mid; while(r>=l) { mid=(l+r)>>1; if(hashval[mid]==m) return mid; if(hashval[mid]<m) l=mid+1; else r=mid-1; } } void pushUp(int rt) { tree[rt].lp=tree[rt<<1].lp; tree[rt].rp=tree[rt<<1|1].rp; tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num; tree[rt].len=tree[rt<<1].len+tree[rt<<1|1].len; if(tree[rt<<1].rp==1 && tree[rt<<1|1].lp==1) tree[rt].num--; } void build(int rt,int L,int R) { tree[rt].cnt=tree[rt].lp=tree[rt].rp=tree[rt].num=tree[rt].len=0; if(L+1==R) return; int mid=(L+R)>>1; build(lson); build(rson); } void update(int rt,int L,int R,int l,int r,int p) { if(l<=L&&R<=r) { tree[rt].cnt+=p; if(tree[rt].cnt) { tree[rt].lp=tree[rt].rp=1; tree[rt].num=1; tree[rt].len=hashval[R]-hashval[L]; } else { tree[rt].lp=tree[rt].rp=0; tree[rt].num=tree[rt].len=0; } return; } int mid=(L+R)>>1; if(l<mid) update(lson,l,r,p); if(r>mid) update(rson,l,r,p); pushUp(rt); } int main() { int x1,y1,x2,y2; while(scanf("%d",&n)!=EOF) { cnt=idx=0; for(int i=1; i<=n; i++) { scanf("%d%d%d%d",&x1,&y1,&x2,&y2); line[2*i-1].l=x1;line[2*i-1].r=x2;line[2*i-1].y=y1;line[2*i-1].tp=1; line[2*i].l=x1;line[2*i].r=x2;line[2*i].y=y2;line[2*i].tp=-1; xval[++idx]=x1; xval[++idx]=x2; } lnum=2*n; sort(line+1,line+lnum+1); sort(xval+1,xval+idx+1); //对x坐标进行离散 hashval[++cnt]=xval[1]; for(int i=2; i<=idx; i++) { if(xval[i]!=xval[i-1]) { hashval[++cnt]=xval[i]; } } build(1,1,cnt); long long ans=0; int last=0; //last记录插入上一次扫描线的区间总长度 int x,y; for(int i=1; i<=lnum; i++) { ans+=tree[1].num*2*(line[i].y-line[i-1].y); x=line[i].l; y=line[i].r; x=binarySearch(x); y=binarySearch(y); for(int j=x; j<=y-1; j++) update(1,1,cnt,j,j+1,line[i].tp); ans+=abs(tree[1].len-last); last=tree[1].len; } printf("%I64d ",ans); } return 0; }
后来写了个区间更新的,代码贴上:
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cmath> #define lson rt<<1,L,mid #define rson rt<<1|1,mid+1,R /* 区间更新AC 15ms 把之前写的单点更新的提交了下,0ms。。。 区间更新反而比单点更新慢。。。郁闷。。。 这次建立的叶子节点是(a,a),所以在二分查找对应的区间(a,b)时,右端点b要减1 */ using namespace std; const int maxn=5000+5; int n,cnt; int hashx[maxn<<1]; struct Node{ int cnt; int lp,rp; int num; int len; }tree[maxn<<2]; struct Line{ int l,r,y; int tp; bool operator<(const Line tmp)const{ if(y==tmp.y){ return tp>tmp.tp; } else return y<tmp.y; } }line[maxn<<1]; void build(int rt,int L,int R){ tree[rt].cnt=tree[rt].lp=tree[rt].rp=tree[rt].num=0; tree[rt].len=0; if(L==R) return; int mid=(L+R)>>1; build(lson); build(rson); } void pushUp(int rt,int L,int R){ if(tree[rt].cnt){ tree[rt].len=hashx[R+1]-hashx[L]; tree[rt].lp=tree[rt].rp=1; tree[rt].num=1; } else{ if(L==R){ tree[rt].len=0; tree[rt].lp=tree[rt].rp=tree[rt].num=0; } else{ //父节点cnt=0,但可能子节点有cnt不为0的,所以父亲要从子节点处获得更新 tree[rt].lp=tree[rt<<1].lp; tree[rt].rp=tree[rt<<1|1].rp; tree[rt].len=tree[rt<<1].len+tree[rt<<1|1].len; tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num; if(tree[rt<<1].rp && tree[rt<<1|1].lp) tree[rt].num--; //减去一个重复计算的 } } } void update(int rt,int L,int R,int l,int r,int val){ if(l<=L&&R<=r){ tree[rt].cnt+=val; pushUp(rt,L,R); //区间更新这里不能忘 return; } int mid=(L+R)>>1; if(l<=mid) update(lson,l,r,val); if(r>mid) update(rson,l,r,val); pushUp(rt,L,R); } int binarySearch(int x,int n){ int l=0,r=n+1,mid; while(r-l>1){ mid=(l+r)>>1; if(hashx[mid]<=x) l=mid; else r=mid; } return l; } int main() { int x1,x2,y1,y2; while(scanf("%d",&n)!=EOF){ cnt=1; for(int i=1;i<=n;i++){ scanf("%d%d%d%d",&x1,&y1,&x2,&y2); line[2*i-1].y=y1;line[2*i-1].l=x1;line[2*i-1].r=x2;line[2*i-1].tp=1; line[2*i].y=y2;line[2*i].l=x1;line[2*i].r=x2;line[2*i].tp=-1; hashx[cnt++]=x1; hashx[cnt++]=x2; } n=n*2; sort(hashx+1,hashx+cnt); sort(line+1,line+n+1); int idx=1; for(int i=2;i<=n;i++){ if(hashx[i]!=hashx[i-1]) hashx[++idx]=hashx[i]; } build(1,1,idx); long long ans=0; int last=0; for(int i=1;i<=n;i++){ ans+=(line[i].y-line[i-1].y)*2*tree[1].num; int a=binarySearch(line[i].l,idx); int b=binarySearch(line[i].r,idx)-1; update(1,1,idx,a,b,line[i].tp); ans+=abs(tree[1].len-last); last=tree[1].len; } printf("%I64d ",ans); } return 0; }