/* ymc 2009/4/9 hdu 1828 picture 线段树 参考99年国家队IOI论文集 题目大意: 给定n个边与坐标轴平行的矩形,求这些矩形的覆盖的形状的周长。 思想: 把所有矩形的左右边按x轴坐标从小大大排列,并且标记是左边还是右边,存在line[]中。 扫描矩形的所有y坐标,排列,按照这些y坐标切割y轴,得到2*n-1条线段,存在y[]中, 用这2*n-1条线段建立线段树,跟节点为tree[1]。 扫描矩形的所有x坐标,排列。放在x[1]到x[2n-1], 并且x[0]=x[1](计算时不用考虑第一次特殊情况)。 扫描line[],如果line[i]为左边,则插入线段树,否则从线段树中删除。 插入算法:线段line[i]插入到tree[p]中 如果line[i]完全覆盖tree[p],插入到tree[p]的树根。 再考虑插入到tree[p]的左子树与右子树。 同时记录tree[p]的跟节点需要更新(update=false) 删除算法:从tree[p]中删除line[i] 如果tree[p]完全被line[i]覆盖,从树根中删除。 再考虑从左子树与又子树中删除。 同时记录跟节点需要更新。 更新算法:更新树tree[p] 若tree被完全覆盖(count>0) 则 len=tree[p]的代表的线段的长度 左右端点都被覆盖(父节点可能要用到这个信息) 包含的线段数lines为1。 否则 len=左子树的len+又子树的len 左右端点的覆盖情况要看左子树的左端点和右子树的右端点。 包含的线段数lines为左子树和右子树的线段之和。(考虑中间的两段是否合成一段) 初始ans=0(周长); 每次插入或者删除线段line[i]时,同时计算ans的增长。具体如下: ans+=tree[1].lines*2*(x[i+1]-x[i1]) ans+=2*(更新信息前后,tree[1]中测度差的绝对值) */ #include <iostream> #include <vector> #include <algorithm> #include <cmath> using namespace std; const int N=20000; struct Line //表示平行与y轴的边 { int y1,y2; int x; bool left;//是否为矩形的左边 bool operator <(const Line t) const { return x<t.x; } }; struct Node//线段树的节点 { // int l,r; int count; //节点是被线段完全覆盖的次数 int len; //节点上线段的测度 int lines; //节点上所包含的线段的段数 int lcover,rcover; //节点的左右端点是否被覆盖 bool update; //节点是否更新过 }; Node tree[4*N]; Line line[2*N]; int x[2*N],y[2*N];//x,y轴上的分割点, int n; void Build(int p,int left,int right)//left,right为y轴上分割点的下标 { tree[p].count=0; tree[p].len=0; tree[p].lines=0; tree[p].lcover=0; tree[p].rcover=0; tree[p].update=true; if(left==right-1)//叶节点 return; int m=(left+right)/2; Build(2*p,left,m); Build(2*p+1,m,right); } void Insert(int p,int l,int r,int y1,int y2) { if(y1<=y[l]&&y[r]<=y2)//覆盖整个树p { tree[p].count++; tree[p].update=false; //return; 这里不能返回,不然WA } if(l==r-1) return; int m=(l+r)/2; if(y1<y[m])//插入到左子树 { tree[p].update=false; Insert(2*p,l,m,y1,y2); } if(y[m]<y2)//插入到右子树 { tree[p].update=false; Insert(2*p+1,m,r,y1,y2); } } void Delete(int p,int l,int r,int y1,int y2) { if(y1<=y[l]&&y[r]<=y2)//从跟中删除 { tree[p].count--; tree[p].update=false; //return; 这里不能返回,不然WA } if(l==r-1) return; int m=(l+r)/2; if(y1<y[m])//从左子树中删除 { tree[p].update=false; Delete(2*p,l,m,y1,y2); } if(y[m]<y2)//从右子树中删除 { tree[p].update=false; Delete(2*p+1,m,r,y1,y2); } } void Update(int p,int l,int r) { if(tree[p].update)//剪枝,不然TLE return; if(tree[p].count>0)//被完全覆盖 { tree[p].len=y[r]-y[l]; tree[p].lcover=true; tree[p].rcover=true; tree[p].lines=1; } else if(l==r-1) //叶节点并且count==0 { tree[p].len=0; tree[p].lcover=false; tree[p].rcover=false; tree[p].lines=0; } else//不是也节点并且没被完全覆盖 { int m=(l+r)/2; Update(2*p,l,m);//更新子节点 Update(2*p+1,m,r); tree[p].len=tree[2*p].len+tree[2*p+1].len;//从子节点得到根节点的信息 tree[p].lcover=tree[2*p].lcover; tree[p].rcover=tree[2*p+1].rcover; tree[p].lines=tree[2*p].lines+tree[2*p+1].lines; if(tree[2*p].rcover&&tree[2*p+1].lcover) tree[p].lines--; } } void Init() { int x1,y1,x2,y2; int k; for(int i=1;i<=n;i++) { scanf("%d%d%d%d",&x1,&y1,&x2,&y2); k=2*i-2; x[k+1]=x1;//预留x[0] x[k+2]=x2; y[k]=y1; y[k+1]=y2; line[k].y1=line[k+1].y1=y1; line[k].y2=line[k+1].y2=y2; line[k].x=x1; line[k].left=true; line[k+1].x=x2; line[k+1].left=false; } sort(x+1,x+2*n+1); x[0]=x[1];//在出入与删除时,不需要特殊处理第一个线段 sort(y,y+2*n); sort(line,line+2*n); } void Solve() { int r=2*n-1; Build(1,0,r); int Lastlen=0; int ans=0; for(int i=0;i<2*n;i++)//对所有线段插入或者删除。因为x[0]==x[1],直接处理 { if(line[i].left) Insert(1,0,r,line[i].y1,line[i].y2); else Delete(1,0,r,line[i].y1,line[i].y2); ans+=tree[1].lines*2*(x[i+1]-x[i]);//上下边数*长度 Lastlen=tree[1].len; Update(1,0,r); ans+=fabs((float)Lastlen-tree[1].len);//左右边的长度 } printf("%d\n",ans); } int main() { while(scanf("%d",&n)!=EOF) { if(n==0) { printf("0\n"); continue; } Init(); Solve(); } }