HDU 1542 & POJ 1151 Atlantis【线段树扫描线】

来源:互联网 发布:男士泳衣 知乎 编辑:程序博客网 时间:2024/05/18 03:34

扫描线用于求若干个相交矩形的面积并,因为用几何方法在相交的情况复杂的时候难以计算。下面给2个矩形的情况做例子

给定2个矩形对角的点坐标,则下图的面积为图2的三种颜色面积和

图1

图2    

现在假设有一条竖着的线从左边往右边扫,扫到矩阵的边时,若是入边,将边这一个区间的cover属性+1,出边则-1

图中圈圈的数字是第几条边,花括号的数是cover值。如图扫描线经过②号边(入边)时最上面部分是1,下面还是1,中间是2,到出边就又减了


图3

则扫描到②号线时,计算一次面积,长为②号线的x减去①号线(前面一条线)的x,高为[Ymin,Ymax]中所有cover为正的高度和

(在这里即为①号线的Yup,Ydown差)。这样重复下去即求得面积和。

放线段树上,区间端点为各条边的上下2个y值,每个区间也都有个cover属性表示覆盖的次数,如上所提。

图4

假设点为[1,1],[2,2],[3,3],[4,4](按图1的情况),则有上图。①号线为[1,3],②为[2,4]...

看这图的第二行,前面一个区间是[1,2],后面是[2,4],而不是{3,4],是因为在矩形里,区间是连续的,[2,3]是存在的,如果为mid+1则[2,3]会被弄没。

第三行:[1,2]没有再继续分成[1,1]和[2,2],是因为这样的点区间没有意义,一条线当然要两个点

然而区间端点并不一定是这么好的整数,即可能是浮点数,还可能坐标很大,所以需要对这些区间的Y值离散化(这里不解释,可见),离散化后就跟上图的1,2,3,4一样了。


NEUQ扫描线-视频链接

HDU 1542 Atlantis 也是POJ 1151 Atlantis  不过POJ用G++交的话记得是%.2f而不是%.2lf

思路一:保存区间时区间端点都用double类型,避免离散化的麻烦。然后只对叶区间处理,如扫描图3里的第②条线时,

面积为②号线上cover为1、为2、为1的三段这和。

这种情况下求每一部分面积时:长为当前线的x减去前面一条线的x,代码里的tree[i].x保存上一条线的x值,可以这样做是因为每次求第i条线时,

上一条线已经扫过,在建树时设置x为-1表示没有上一条。

#include <cstdio>#include <cmath>#include <cstring>#include <string>#include <set>#include <map>#include <stack>#include <queue>#include <vector>#include <iostream>#include <iomanip>#include <algorithm>using namespace std;#define ll __int64#define INF 0x7FFFFFFF#define INT_MIN -(1<<31)#define eps 10^(-6)#define Q_CIN ios::sync_with_stdio(false);#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )#define CLR( a , x ) memset ( a , x , sizeof (a) );#define RE freopen("1.in","r",stdin);#define WE freopen("1.out","w",stdout);#define MOD 10009#define debug(x) cout<<#x<<":"<<(x)<<endl;//#define lson i<<1,l,m//#define rson i<<1|1,m+1,rconst int maxn=105;const int maxN=100010;double y[maxn<<1];struct Line{    double x;    double y_up,y_down;    int dir;        //1为入边,-1为出边}L[maxn<<1];         //2*n的边数struct Tree{    double y_up,y_down,x;       //x为上一条线段的x值    int cover;    bool leaf;          //是否为叶区间}tree[maxN<<2];bool cmp(Line a,Line b){    return a.x<b.x;}void build(int i,int l,int r){    tree[i].x=-1;tree[i].y_up=y[r];tree[i].y_down=y[l];tree[i].cover=0;tree[i].leaf=false;    if(l+1==r)      //[1,2]这样的区间就不分下去了    {        tree[i].leaf=true;        return;    }    int m=(l+r)>>1;    build(i<<1,l,m);    build(i<<1|1,m,r);      //区间是连续的,所以右区间的l要与左区间的r相同}double update(int i,double x,double y_down,double y_up,int dir){    if(y_down>=tree[i].y_up||y_up<=tree[i].y_down)    //如在[1,2]里查询[3,4]直接返回0        return 0.0;    if(tree[i].leaf)        //非叶区间不管    {        if(tree[i].cover>0)     //有覆盖到,求面积        {            double xx=tree[i].x;            double ch=(x-xx)*(tree[i].y_up-tree[i].y_down);            tree[i].cover+=dir;     //边覆盖次数            tree[i].x=x;            return ch;        }else        {            tree[i].x=x;            //设x为x            tree[i].cover+=dir;            return 0.0;        }    }    double s1=update(i<<1,x,y_down,y_up,dir);    double s2=update(i<<1|1,x,y_down,y_up,dir);    return s1+s2;}int main(){    int n;    double x1,x2,y1,y2;    int te=1;//    RE    while(cin>>n,n)    {        int cnt=1;        FOR(i,1,n)        {            cin>>x1>>y1>>x2>>y2;            L[cnt].x=x1;L[cnt].y_down=y1;L[cnt].y_up=y2;L[cnt].dir=1;y[cnt]=y1;cnt++;            L[cnt].x=x2;L[cnt].y_down=y1;L[cnt].y_up=y2;L[cnt].dir=-1;y[cnt]=y2;cnt++;        }//        sort(L+1,L+cnt,cmp);//        sort(y+1,y+cnt);//        build(1,1,cnt-1);        sort(L+1,L+2*n+1,cmp);      //线段按x排序        sort(y+1,y+2*n+1);          //y数组升序        build(1,1,2*n);        double ans=0.0;        FOR(i,1,2*n)            ans+=update(1,L[i].x,L[i].y_down,L[i].y_up,L[i].dir);        cout<<"Test case #"<<te++<<endl;        cout<<setiosflags(ios::fixed);        cout<<"Total explored area: "<<setprecision(2)<<ans<<endl<<endl;    }    return 0;}

思路二(主流一点):给每个区间增加一个len属性,表示该区间能与下一条线段计算用的高度,然后顺势每次插入线段就用整个大区间(树)与下一条能算的高度求出面积。

(区间存端点有点离散化的思想,把区间存的都变成了常规的整数l,r)如插入②时,因为有y数组排序,插入的区间就是【2,4】,则【2,4】能与下一条计算的是y[4]-y[2],然后通过pushup把【1,2】能计算的也加进来,就可以求②和③中间的面积了。因为区间的l,r是int,所以插入的线段的区间需要用二分在y数组里找出下标。

#include <cstdio>#include <cmath>#include <cstring>#include <string>#include <set>#include <map>#include <stack>#include <queue>#include <vector>#include <iostream>#include <iomanip>#include <algorithm>using namespace std;#define ll __int64#define INF 0x7FFFFFFF#define INT_MIN -(1<<31)#define eps 10^(-6)#define Q_CIN ios::sync_with_stdio(false);#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )#define CLR( a , x ) memset ( a , x , sizeof (a) );#define RE freopen("1.in","r",stdin);#define WE freopen("1.out","w",stdout);#define MOD 10009#define debug(x) cout<<#x<<":"<<(x)<<endl;//#define lson i<<1,l,m//#define rson i<<1|1,m+1,rconst int maxn=105;const int maxN=100010;double y[maxn<<1];int ulen;struct Line{    double x;    double y_up,y_down;    int dir;        //1为入边,-1为出边}L[maxn<<1];         //2*n的边数struct Tree{    int l,r;    int cover;    double len;}tree[maxN<<2];bool cmp(Line a,Line b){    return a.x<b.x;}void build(int i,int l,int r){    tree[i].l=l;    tree[i].r=r;    tree[i].cover=0;    tree[i].len=0.0;    if(l+1==r)        return;    int m=(l+r)>>1;    build(i<<1,l,m);    build(i<<1|1,m,r);}int find(double x){    int l=1,r=ulen;    while(l<=r)    {        int mid=(l+r)/2;        if(fabs(y[mid]-x)<1e-6) //        if(y[mid]==x)            return mid;        if(y[mid]>x)            r=mid-1;        else            l=mid+1;    }    return l;}void fun(int i)     //包含pushup和完全覆盖的情况下更新{    if(tree[i].cover>0)     //有覆盖则整一段可以做为下一条线段计算用        tree[i].len=(y[tree[i].r]-y[tree[i].l]);    else if(tree[i].l+1==tree[i].r)     //叶子        tree[i].len=0;    else tree[i].len=tree[i<<1].len+tree[i<<1|1].len;}void update(int i,int l,int r,int dir){    if(tree[i].l>r||tree[i].r<l)        //完全不搭        return;    if(l<=tree[i].l&&tree[i].r<=r)  //完全包含    {        tree[i].cover+=dir;        fun(i);        return;    }    update(i<<1,l,r,dir);    update(i<<1|1,l,r,dir);    fun(i);}int main(){    int n;    double x1,x2,y1,y2;    int te=1;    RE    while(cin>>n,n)    {        int cnt=1;        FOR(i,1,n)        {            cin>>x1>>y1>>x2>>y2;            L[cnt].x=x1;L[cnt].y_down=y1;L[cnt].y_up=y2;L[cnt].dir=1;y[cnt]=y1;cnt++;            L[cnt].x=x2;L[cnt].y_down=y1;L[cnt].y_up=y2;L[cnt].dir=-1;y[cnt]=y2;cnt++;        }       //cnt==2*n+1        sort(L+1,L+cnt,cmp);      //线段按x排序        sort(y+1,y+cnt);          //y数组保存y值,升序,才与线段树区间对应        ulen=1;        y[0]=-100005.0;        for(int i=1;i<cnt;i++)      //y数组去重,默认y[0]与y[1]不同        {            if(y[i]!=y[i-1])            {                y[ulen++]=y[i];            }        }        ulen--;         //  !!!!!        build(1,1,ulen);        double ans=0.0;        FOR(i,1,cnt-1)        {                       //在新线的y区间插入新cover(dir)            int a=find(L[i].y_down);    //在y数组里找y_down的下标            int b=find(L[i].y_up);            update(1,a,b,L[i].dir);             //每插入一条就把这条与下一条能算的算起来            ans+=tree[1].len*(L[i+1].x-L[i].x); //tree[1].len为整个树能与i+1条线计算的,因为有pushup操作        }        cout<<"Test case #"<<te++<<endl;        cout<<setiosflags(ios::fixed);        cout<<"Total explored area: "<<setprecision(2)<<ans<<endl<<endl;    }    return 0;}
既然是整型的l,r,那就按常规的线段树飘逸去吧,把tree结构体省了,添加参数代替。

#include <cstdio>#include <cmath>#include <cstring>#include <string>#include <set>#include <map>#include <stack>#include <queue>#include <vector>#include <iostream>#include <iomanip>#include <algorithm>using namespace std;#define ll __int64#define INF 0x7FFFFFFF#define INT_MIN -(1<<31)#define eps 10^(-6)#define Q_CIN ios::sync_with_stdio(false);#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )#define CLR( a , x ) memset ( a , x , sizeof (a) );#define RE freopen("1.in","r",stdin);#define WE freopen("1.out","w",stdout);#define MOD 10009#define debug(x) cout<<#x<<":"<<(x)<<endl;#define lson i<<1,l,m#define rson i<<1|1,m,rconst int maxn=105;const int maxN=100010;double y[maxn<<1];int ulen;struct Line{    double x;    double y_up,y_down;    int dir;        //1为入边,-1为出边    bool operator <(const struct Line &another)const{        return x<another.x;    }}L[maxn<<1];int cover[maxN<<2];double len[maxN<<2];void build(int i,int l,int r){    cover[i]=0;len[i]=0.0;    if(l+1==r)        return;    int m=(l+r)>>1;    build(lson);    build(rson);}void fun(int i,int l,int r)     //包含pushup和完全覆盖的情况下更新{    if(cover[i]>0)      len[i]=(y[r]-y[l]);    else if(l+1==r) len[i]=0;    else len[i]=len[i<<1]+len[i<<1|1];}void update(int i,int l,int r,int L,int R,int dir){    if(L<=l&&r<=R)    {        cover[i]+=dir;        fun(i,l,r);        return;    }    int m=(l+r)>>1;    if(L<m)         //注意这里没有等号,如[1,4]里插入[2,4],不能去左子区间[1,2]找        update(lson,L,R,dir);    if(R>m)        update(rson,L,R,dir);    fun(i,l,r);}int main(){    int n;    double x1,x2,y1,y2;    int te=1;//    RE    while(cin>>n,n)    {        int cnt=1;        FOR(i,1,n)        {            cin>>x1>>y1>>x2>>y2;            L[cnt].x=x1;L[cnt].y_down=y1;L[cnt].y_up=y2;L[cnt].dir=1;y[cnt]=y1;cnt++;            L[cnt].x=x2;L[cnt].y_down=y1;L[cnt].y_up=y2;L[cnt].dir=-1;y[cnt]=y2;cnt++;        }       //cnt==2*n+1        sort(L+1,L+cnt);        sort(y+1,y+cnt);        ulen=1;        y[0]=-100005.0;        for(int i=1;i<cnt;i++)            if(y[i]!=y[i-1])                y[ulen++]=y[i];        ulen--;        build(1,1,ulen);        double ans=0.0;        FOR(i,1,cnt-1)        {            int a=lower_bound(y+1,y+ulen,L[i].y_down)-y;            int b=lower_bound(y+1,y+ulen,L[i].y_up)-y;//因为知道元素一定存在            update(1,1,ulen,a,b,L[i].dir);            ans+=len[1]*(L[i+1].x-L[i].x);        }        printf("Test case #%d\nTotal explored area: %.2lf\n\n",te++,ans);    }    return 0;}



0 0
原创粉丝点击