【线段树】扫描线学习笔记

之后有一回在luogu做了一道同样求矩形周长的题,用了下面的模板,但是有组数据过不了,需要做如下修改:

重载运算符函数修改成:  bool operator <(const P&p)const{return h==p.h?ju<p.ju:h<p.h;}


之前并不能理解扫描线这种东西,真的以为是条线,还在想这东西到底要怎么实现?

结果理解后,好像还真是条线。

好吧。其实是以一个坐标轴的点确定的直线+长度,直线和长度便确定了线段,也有了值。

扫描线应该说是靠顺序查询轴线点来实现的。

看了一个大佬的博客,受益匪浅。我是看了这个博客才明白扫描线这东西的做法。

总的来说,是先确定一个轴线为遍历方向,以该方向从小到大排序点,

然后扫描线就是:以确定的轴线方向(设为第一坐标轴)为扫描方向,按从小到大的顺序(通常都是从小到大),逐个遍历目标点的坐标,以该点的另一个坐标轴的方向(设为第二坐标轴)确定另一条直线,然后在遍历的过程中,以某种方法(比如线段树),尝试确定当前点的第二坐标轴方向直线的有效长度,

  有效长度指的是:当前直线到下一条直线所围面积的底边(即这根直线上的边)的长度。

然后以这个当前有效长度,来实现我们的目的。好比如,计算面积,就是逐个遍历,用当前有效长度,乘以与下一条直线间的距离(即第一坐标轴相邻两点的距离)。

我看的博客的代码,当前有效长度是以sum[1]数组记录。用线段树更新。

另外,有将第一坐标轴的点离散化。

然后根据那个博客,我改了里边的代码,做了我扫描线的第一题,洛谷P1904

给定以x轴为下底边的矩形的信息,求矩形轮廓外轮廓。

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e3+50;
int mark[maxn<<2];//记录某个区间的下底边个数
int sum[maxn<<2];//记录某个区间的下底边总长度
int a[maxn<<1];//对h进行离散化
//以纵坐标作为线段(区间),对纵坐标线段进行扫描 
struct P{
    int x,h,ju;//以h为扫描线平行边,x为扫描方向,ju=-1为左 ju=1为右
    bool operator <(const P&p)const{return x<p.x;}
    P(){}
    P(int xx,int hh,int juu){x=xx;h=hh;ju=juu;}
}s[maxn<<1];
void upfather(int k,int l,int r){
    if(mark[k])sum[k]=a[r+1]-a[l];//
    else if(l==r)sum[k]=0;
    else sum[k]=sum[k<<1]+sum[k<<1|1];
}//n k
void update(int L,int R,int ju,int k,int l,int r){
    if(L<=l&&r<=R){
        mark[k]+=ju;
        upfather(k,l,r);
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)update(L,R,ju,k<<1,l,mid);
    if(R>mid)update(L,R,ju,k<<1|1,mid+1,r);
    upfather(k,l,r);
}
int search(int key,int *x,int k){
    int l=0,r=k-1;
    while(l<=r){
        int mid=(l+r)>>1;
        if(x[mid]==key)return mid;
        if(x[mid]>key)r=mid-1;
        else l=mid+1; 
    }
    return -1;
}
struct PP{
    int x,y;
    PP(){}
    PP(int xx,int yy){x=xx;y=yy;}
}ans[maxn<<2];
int solve(int tot,int up)//tot是边数 up是点数 
{
    int upp=0,tans=0;
    for(int i=0;i<tot;i++){
        int R=search(s[i].h,a,up)-1;
        update(0,R,s[i].ju,1,0,up-1);
        if(sum[1]==tans)continue;
        if(ans[upp-1].x==s[i].x&&ans[upp-1].y==tans)upp--;
        else ans[upp++]=PP(s[i].x,tans);
        if(ans[upp-1].x==s[i].x&&ans[upp-1].y==sum[1])upp--;
        else ans[upp++]=PP(s[i].x,sum[1]);
        tans=sum[1];
    }
    return upp;
}
int main()
{
    int n,tot=0,l,h,r,up=0;
    a[up++]=0;
//    scanf("%d",&n);
    while(~scanf("%d%d%d",&l,&h,&r))
//    while(tot<n*2)
    {
//        scanf("%d%d%d",&l,&h,&r);
        a[up++]=h;
        s[tot++]=P(l,h,-1);
        s[tot++]=P(r,h,1);
    }
    sort(a,a+up);
    sort(s,s+tot);
    up=unique(a,a+up)-a;
    int upp=solve(tot,up);
    for(int i=0;i<upp;i++)
    {
        if(i&1)printf("%d ",ans[i].y);
        else printf("%d ",ans[i].x);
//         printf("%d% d
",ans[i].x,ans[i].y);
    }
}

2019-09-04

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

扫描线第二题 HDU1542

给定矩形信息,求总面积。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e2+50;
int mark[maxn<<2];//记录某个区间的下底边个数
double sum[maxn<<2];//记录某个区间的下底边总长度
double a[maxn<<1];//对x进行离散化
//
struct P{
    double l,r,h;
    int ju;
    bool operator <(const P&p)const{return h<p.h;}
    P(){}
    P(double ll,double rr,double hh,int juu)
    {l=ll;r=rr;h=hh;ju=juu;}
}s[maxn<<1];

void upfather(int k,int l,int r){
    if(mark[k])sum[k]=a[r+1]-a[l];
    else if(l==r)sum[k]=0;
    else sum[k]=sum[k<<1]+sum[k<<1|1];
}
void update(int L,int R,int ju,int k,int l,int r){
    if(L<=l&&r<=R){
        mark[k]+=ju;
        upfather(k,l,r);
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)update(L,R,ju,k<<1,l,mid);
    if(R>mid)update(L,R,ju,k<<1|1,mid+1,r);
    upfather(k,l,r);
}
int search(double key,double *x,int k){
    int l=0,r=k-1;
    while(l<=r){
        int mid=(l+r)>>1;
        if(x[mid]==key)return mid;
        if(x[mid]>key)r=mid-1;
        else l=mid+1; 
    }
    return -1;
}

double solve(int tot,int up)//tot是边数 up是点数 
{
    double ans=0;
    for(int i=0;i<tot;i++){
        int L=search(s[i].l,a,up);
        int R=search(s[i].r,a,up)-1;
        update(L,R,s[i].ju,1,0,up-1);
        ans+=sum[1]*(s[i+1].h-s[i].h);
    }
    return ans;
}

int main()
{
    int n,cas=0;
    while(scanf("%d",&n)!=EOF&&n)
    {
        cas++;
        memset(a,0,sizeof(a));
        memset(s,0,sizeof(s));
        int i,tot=0;
        double x1,y1,x2,y2;
        for(i=0;i<n;i++)
        {
            scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
            a[tot]=x1;
            s[tot++]=P(x1,x2,y1,-1);
            a[tot]=x2;
            s[tot++]=P(x1,x2,y2,1);
        }
        sort(a,a+tot);
        sort(s,s+tot);
        int up=unique(a,a+tot)-a;
        printf("Test case #%d
",cas);
        printf("Total explored area: %.2lf

",solve(tot,up));
    }
}

2019-09-05

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

第三题,POJ1177

给定矩形信息,求周长。

(好的做法应该是用线段树的连通性维护当前扫描线有几段,然后另一方向的长度直接等于2*num*h,然而我试了,不会做,只能老老实实再在另一方向扫描一遍。)

#include<bits/stdc++.h>
#define debug printf("!");
using namespace std;
typedef long long ll;
const int maxn=1e4+50;
const int inf=0x3f3f3f3f;


int mark[maxn<<2];//记录某个区间的下底边个数
ll sum[maxn<<2];//记录某个区间的下底边总长度
int a1[maxn<<1],a2[maxn<<1];//对x进行离散化
//
struct P{
    int l,r,h;
    int ju;
    bool operator <(const P&p)const{return h==p.h?ju<p.ju:h<p.h;}//
    /*
    排序需要ju作为第二关键字
    测试样例
    2
    0 0 4 4
    0 4 4 8
    
    使得上面图形的底边 和 下面图形的顶边 在重边时避免多算 
    
    */
}s1[maxn<<1],s2[maxn<<1];
void upfather(int k,int l,int r,int a[]){
    if(mark[k])sum[k]=a[r+1]-a[l];
    else if(l==r)sum[k]=0;
    else sum[k]=sum[k<<1]+sum[k<<1|1];
}
void update(int L,int R,int ju,int k,int l,int r,int a[]){
    if(L<=l&&r<=R){
        mark[k]+=ju;
        upfather(k,l,r,a);
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)update(L,R,ju,k<<1,l,mid,a);
    if(R>mid)update(L,R,ju,k<<1|1,mid+1,r,a);
    upfather(k,l,r,a);
}
int search(int key,int *x,int k){
    int l=0,r=k-1;
    while(l<=r){
        int mid=(l+r)>>1;
        if(x[mid]==key)return mid;
        if(x[mid]>key)r=mid-1;
        else l=mid+1; 
    }
    return -1;
}

ll solve(int tot,int up,P s[],int a[])//tot是边数 up是点数 
{
    ll ans=0,tans=0;
    for(int i=0;i<tot;i++){
        int L=search(s[i].l,a,up);
        int R=search(s[i].r,a,up)-1;
        update(L,R,s[i].ju,1,0,up-1,a);
        if(sum[1]-tans>=0)ans+=sum[1]-tans;
        else ans-=sum[1]-tans;
        tans=sum[1];
    }
    return ans;
}
int main()
{
    int i,n,tot=0,up;
    int x1,y1,x2,y2;
    ll ans=0;
    scanf("%d",&n);
    for(i=0;i<n;i++)
    {
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        a1[tot]=x1;
        a2[tot]=y1;
        s1[tot]=P{x1,x2,y1,-1};
        s2[tot++]=P{y1,y2,x1,-1};
        a1[tot]=x2;
        a2[tot]=y2;
        s1[tot]=P{x1,x2,y2,1};
        s2[tot++]=P{y1,y2,x2,1};
    }
    sort(a1,a1+tot);sort(a2,a2+tot);
    sort(s1,s1+tot);sort(s2,s2+tot);
    up=unique(a1,a1+tot)-a1;
    ans+=solve(tot,up,s1,a1);
    memset(sum,0,sizeof(sum));
    memset(mark,0,sizeof(mark));
    up=unique(a2,a2+tot)-a2;
    ans+=solve(tot,up,s2,a2);
    printf("%lld
",ans);
}

2019-09-05 20:08:06

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

第四题,洛谷P382

跟第一题几乎一样,改点输入输出和一个小细节就可以了:D。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+50;
int mark[maxn<<2];//记录某个区间的下底边个数
int sum[maxn<<2];//记录某个区间的下底边总长度
int a[maxn<<1];//对h进行离散化
//以纵坐标作为线段(区间),对纵坐标线段进行扫描 
struct P{
    int x,h,ju;//以h为扫描线平行边,x为扫描方向,ju=-1为左 ju=1为右
    bool operator <(const P&p)const{return x<p.x;}
    P(){}
    P(int xx,int hh,int juu){x=xx;h=hh;ju=juu;}
}s[maxn<<1];
void upfather(int k,int l,int r){
    if(mark[k])sum[k]=a[r+1]-a[l];//
    else if(l==r)sum[k]=0;
    else sum[k]=sum[k<<1]+sum[k<<1|1];
}//n k
void update(int L,int R,int ju,int k,int l,int r){
    if(L<=l&&r<=R){
        mark[k]+=ju;
        upfather(k,l,r);
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)update(L,R,ju,k<<1,l,mid);
    if(R>mid)update(L,R,ju,k<<1|1,mid+1,r);
    upfather(k,l,r);
}
int search(int key,int *x,int k){
    int l=0,r=k-1;
    while(l<=r){
        int mid=(l+r)>>1;
        if(x[mid]==key)return mid;
        if(x[mid]>key)r=mid-1;
        else l=mid+1; 
    }
    return -1;
}
struct PP{
    int x,y;
    PP(){}
    PP(int xx,int yy){x=xx;y=yy;}
}ans[maxn<<2];
int solve(int tot,int up)//tot是边数 up是点数 
{
    int upp=0,tans=0;
    for(int i=0;i<tot;i++){
        int R=search(s[i].h,a,up)-1;
        update(0,R,s[i].ju,1,0,up-1);
        if(sum[1]==tans)continue;
        if(ans[upp-1].x==s[i].x&&ans[upp-1].y==tans&&i!=0)upp--;
        else ans[upp++]=PP(s[i].x,tans);
        if(ans[upp-1].x==s[i].x&&ans[upp-1].y==sum[1]&&i!=0)upp--;
        else ans[upp++]=PP(s[i].x,sum[1]);
        tans=sum[1];
    }
    return upp;
}
int main()
{
    int n,tot=0,l,h,r,up=0;
    a[up++]=0;
    scanf("%d",&n);
//    while(~scanf("%d%d%d",&l,&h,&r))
    while(tot<n*2)
    {
        scanf("%d%d%d",&h,&l,&r);
        a[up++]=h;
        s[tot++]=P(l,h,-1);
        s[tot++]=P(r,h,1);
    }
    sort(a,a+up);
    sort(s,s+tot);
    up=unique(a,a+up)-a;
    int upp=solve(tot,up);
    printf("%d
",upp);
    for(int i=0;i<upp;i++)
    {
//        if(i&1)printf("%d ",ans[i].y);
//        else printf("%d ",ans[i].x);
         printf("%d% d
",ans[i].x,ans[i].y);
    }
}

2019-09-06 15:02:29

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

原文地址:https://www.cnblogs.com/kkkek/p/11462426.html