• ACM-线段树扫描线总结


    扫描线的基础概念可以看这几篇文章

    http://blog.csdn.net/xingyeyongheng/article/details/8927732

    http://www.cnblogs.com/scau20110726/archive/2013/03/21/2972808.html

    在这里讲一下应用

    1.求矩形的面积并

    以hdu 1542为例:

    给出n个矩阵的对角坐标,求矩阵的面积并。

    这里需要注意这么几点:

    • 离散化

    因为数据跨度很大,所以肯定要将数据离散化,保存在hash数组中。但要小心离散化本身的坑。

    离散化的坑,就是在表示线段树时,我们会把1-4划分为1-2和3-4,如果覆盖1-2和3-4就等同于把整个线段覆盖了,但其实不对,因为2和3中间也是有值的。

     下面这题也遇到这个坑,解决方法是:在update时读入r-1,计算时使用r+1

    • 水平扫描的话,hash的是y坐标,反之亦然
    • hash最好要去重
    #include <iostream>
    #include <string>
    #include <cstring>
    #include <cstdlib>
    #include <cstdio>
    #include <cmath>
    #include <algorithm>
    #include <stack>
    #include <queue>
    #include <cctype>
    #include <vector>
    #include <iterator>
    #include <set>
    #include <map>
    #include <sstream>
    using namespace std;
    
    #define mem(a,b) memset(a,b,sizeof(a))
    #define pf printf
    #define sf scanf
    #define spf sprintf
    #define pb push_back
    #define debug printf("!
    ")
    #define MAXN 200+5
    #define MAX(a,b) a>b?a:b
    #define blank pf("
    ")
    #define LL long long
    #define ALL(x) x.begin(),x.end()
    #define INS(x) inserter(x,x.begin())
    #define pqueue priority_queue
    #define INF 0x3f3f3f3f
    
    #define ls (rt<<1)
    #define rs (rt<<1|1)
    
    int n,m;
    
    double hh[MAXN],col[MAXN<<2],len[MAXN<<2];
    
    struct node
    {
        double l,r,x,c;
        node(){}
        node(double a,double b,double c,double d):l(a),r(b),x(c),c(d){}
        bool operator < (const node &b) const
        {
            return x<b.x;
        }
    }a[MAXN];
    
    void PushUp(int rt,int l,int r)
    {
        if(col[rt])
        {
            len[rt] = hh[r+1] - hh[l];
        }
        else if(l==r) len[rt] = 0;
        else
        {
            len[rt] = len[ls]+len[rs];
        }
    }
    
    void update(int val,int L,int R,int l,int r,int rt)
    {
        if(L<=l && r<=R)
        {
            col[rt] += val;
            PushUp(rt,l,r);
            return;
        }
        int mid = (l+r)>>1;
        if(L <= mid) update(val,L,R,l,mid,ls);
        if(R > mid) update(val,L,R,mid+1,r,rs);
        PushUp(rt,l,r);
    }
    
    int main()
    {
        int i,j,k,t,kase=1;
        while(~sf("%d",&n) && n)
        {
            int v=0;
            double sum = 0;
            for(i=0;i<n;i++)
            {
                double x1,y1,x2,y2;
                sf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
                hh[v] = y1;
                a[v++] = node(y1,y2,x1,1);
                hh[v] = y2;
                a[v++] = node(y1,y2,x2,-1);
            }
            sort(hh,hh+v);
            sort(a,a+v);
            int d = 1;
            for(i=1;i<v;i++)
            {
                if(hh[i]!=hh[i-1]) hh[d++] = hh[i];
            }
            mem(len,0);
            mem(col,0);
            for(i=0;i<v-1;i++)
            {
                int l = lower_bound(hh,hh+d,a[i].l)-hh;
                int r = lower_bound(hh,hh+d,a[i].r)-hh-1;
                update(a[i].c,l,r,0,d-1,1);
                sum+=len[1]*(a[i+1].x-a[i].x);
                //pf("%lf %lf
    ",sum,len[1]);
            }
            pf("Test case #%d
    Total explored area: %.2lf
    
    ",kase++,sum);
        }
    
        return 0;
    }

    2.求矩形周长并

    以hdu 1828为例:

    http://blog.csdn.net/xingyeyongheng/article/details/8931410

    http://www.cnblogs.com/scau20110726/archive/2013/04/13/3018687.html

    http://www.cnblogs.com/scau20110726/archive/2013/04/13/3018702.html

    三篇很不错的文章,基础概念可以看这几个

    从面积变成了周长,方法其实是差不多的。解决这个问题有两种思路:

    1. 对横线,竖线分别做一次处理。分别求出有效覆盖长度,两者之和就是结果。
      • 把矩形分成横线和竖线去处理,可知是完全相同的操作,我们来讲下怎么算出横线部分,竖线部分就是照搬即可。

        将横线保存在一个表中,按横线所处的竖直位置排序(升序),另外每条横线带一个标记值,原矩形的下线为1,上线为-1(对应过去就是插入线段和删除线段)

        从低到高扫描横线,没扫到一条横线就能计算出一部分横线值。计算方法是算出现在总区间的被覆盖的长度,然后求出与上一次的总区间的覆盖长度的差(即相减求绝对值),因为每次添加了一条线段,如果没有没有使总区间覆盖长度发生变化,说明这条线段其实在多边形的内部,被覆盖掉了,不能计算,只要能引起总区间长度发生变化的,说明该线段不被覆盖不被包含,要计算

        而竖线部分的做法是一样的,把竖线保存在一个表中,按水平位置排序(升序),每条横线带一个标记值,原矩形的左线为1,右线为-1,然后同样地操作

    2. 通过记录覆盖的左右端点,以及数量信息,判断是否覆盖,扫描一次的同时求出两线长度。
      • 前面的工作是相同的,把横线保存在一个表中,按竖直位置排序,然后从下往上扫描所有横线,在这个方法中,每次扫描一条横线,都能计算出两不部分值,一部分是横线的,一部分是竖线的

        而计算横线部分的方法和第一种方法是一样的,即求出【现在这次总区间被覆盖的长度和上一次总区间被覆盖的长度的差的绝对值】,另外怎么算竖线部分呢?首先我们要知道添加了这条横线后会有多少条竖线,答案是2*num,所以为什么要记录num呢,因为总区间被num条线段覆盖,那么必定有2*num的端点,这些端点其实就是连着竖线,那么这些竖线的长度是多少呢?

        就是【下一条横线的高度-现在这条横线的高度】,只要把这个高度乘上2*num即可

    我采用的是第二种方法,这里不需要离散化,代码如下:

    #include <iostream>
    #include <string>
    #include <cstring>
    #include <cstdlib>
    #include <cstdio>
    #include <cmath>
    #include <algorithm>
    #include <stack>
    #include <queue>
    #include <cctype>
    #include <vector>
    #include <iterator>
    #include <set>
    #include <map>
    #include <sstream>
    using namespace std;
    
    #define mem(a,b) memset(a,b,sizeof(a))
    #define pf printf
    #define sf scanf
    #define spf sprintf
    #define pb push_back
    #define debug printf("!
    ")
    #define MAXN 20000+5
    #define MAX(a,b) a>b?a:b
    #define blank pf("
    ")
    #define LL long long
    #define ALL(x) x.begin(),x.end()
    #define INS(x) inserter(x,x.begin())
    #define pqueue priority_queue
    #define INF 0x3f3f3f3f
    
    #define ls (rt<<1)
    #define rs (rt<<1|1)
    
    int n,m;
    
    int col[MAXN<<2],len[MAXN<<2],lseg[MAXN<<2],rseg[MAXN<<2],segnum[MAXN<<2];
    
    struct node
    {
        int l,r,x,c;
        node(){}
        node(int a,int b,int c,int d):l(a),r(b),x(c),c(d){}
        bool operator < (const node &b) const
        {
            return x<b.x || x==b.x && c>b.c;
        }
    }a[MAXN];
    
    void PushUp(int rt,int l,int r)
    {
        if(col[rt])
        {
            len[rt] = r-l+1;
            segnum[rt]=2;
            lseg[rt] = rseg[rt] = 1;
        }
        else if(l==r) len[rt] = segnum[rt] = lseg[rt] = rseg[rt] =  0;
        else
        {
            len[rt] = len[ls]+len[rs];
            segnum[rt] = segnum[ls]+segnum[rs];
            lseg[rt] = lseg[ls];
            rseg[rt] = rseg[rs];
            if(lseg[rs] && rseg[ls]) segnum[rt]-=2;
        }
    }
    
    void update(int val,int L,int R,int l,int r,int rt)
    {
        if(L<=l && r<=R)
        {
            col[rt] += val;
            PushUp(rt,l,r);
            return;
        }
        int mid = (l+r)>>1;
        if(L <= mid) update(val,L,R,l,mid,ls);
        if(R > mid) update(val,L,R,mid+1,r,rs);
        PushUp(rt,l,r);
    }
    
    int main()
    {
        int i,j,k,t,kase=1;
        while(~sf("%d",&n) && n)
        {
            int left = 10000,right = -10000,v=0,ans=0,last=0;
            mem(len,0);
            mem(col,0);
            mem(segnum,0);
            mem(lseg,0);
            mem(rseg,0);
            for(i=0;i<n;i++)
            {
                int x1,y1,x2,y2;
                sf("%d%d%d%d",&x1,&y1,&x2,&y2);
                a[v++] = node(y1,y2,x1,1);
                a[v++] = node(y1,y2,x2,-1);
                left = min(left,y1);
                right = max(right,y2);
            }
            //pf("%d %d
    ",left,right);
            sort(a,a+v);
            for(i=0;i<v;i++)
            {
                if(a[i].l < a[i].r) update(a[i].c,a[i].l,a[i].r-1,left,right,1);
                ans+= segnum[1]*(a[i+1].x-a[i].x);
                ans+= abs(len[1]-last);
                last = len[1];
            }
            pf("%d
    ",ans);
        }
    
        return 0;
    }
  • 相关阅读:
    PHP数组的升序降序函数
    overflow样式修改插件——jscrollpane
    li标签之间的空隙问题(转)
    纯CSS制作三角(转)
    图片放大插件——elevatezoom
    git常用命令笔记
    求数组子序列和最大值
    Vim快速入门
    C++求两个整数的最大公约数和最小公倍数
    实验楼-4-Linux文件权限
  • 原文地址:https://www.cnblogs.com/qlky/p/5747719.html
Copyright © 2020-2023  润新知