线段树+扫描线(基本原理)

      这一部分是线段树的一个难点了,这写天做了这么多的这方面的题,一直是稀里糊涂的搞不太明白,但是又得理解,看到了一个别人转载的讲解,贴在这里以便回顾(侵删)。

  扫描线法:

      假想有一条扫描线,从左往右(从右往左),或者从下往上(从上往下)扫描过整个多边形(或者说畸形。。多个矩形叠加后的那个图形)。如果是竖直方向上扫描,则是离散化横坐标,如果是水平方向上扫描,则是离散化纵坐标。下面的分析都是离散化横坐标的,并且从下往上扫描的

扫描之前还需要做一个工作,就是保存好所有矩形的上下边,并且按照它们所处的高度进行排序,另外如果是上边我们给他一个值-1,下边给他一个值1,我们用一个结构体来保存所有的上下边

struct segment 
{ 
double l,r,h; //l,r表示这条上下边的左右坐标,h是这条边所处的高度 
int f; //所赋的值,1或-1 
}

接着扫描线从下往上扫描,每遇到一条上下边就停下来,将这条线段投影到总区间上(总区间就是整个多边形横跨的长度),这个投影对应的其实是个插入和删除线段操作。还记得给他们赋的值1或-1吗,下边是1,扫描到下边的话相当于往总区间插入一条线段,上边-1,扫描到上边相当于在总区间删除一条线段(如果说插入删除比较抽象,那么就直白说,扫描到下边,投影到总区间,对应的那一段的值都要增1,扫描到上边对应的那一段的值都要减1,如果总区间某一段的值为0,说明其实没有线段覆盖到它,为正数则有,那会不会为负数呢?是不可能的,可以自己思考一下)。

每扫描到一条上下边后并投影到总区间后,就判断总区间现在被覆盖的总长度,然后用下一条边的高度减去当前这条边的高度,乘上总区间被覆盖的长度,就能得到一块面积,并依此做下去,就能得到最后的面积

(这个过程其实一点都不难,只是看文字较难体会,建议纸上画图,一画即可明白,下面献上一图希望有帮助) 

这里写图片描述

   


      这里还有做的几道题。    

      二维线段树 (区间并) 查询被覆盖的面积  点击打开链接  简单题

      模板!!!

代码如下:

#include<stdio.h>
#include<string>
#include<algorithm>
#include<iostream>
using namespace std;
const int MAX=210;
int N;
double x[MAX<<2];
struct Node
{
    double l, r;
    double h;
    int flag;
}node[MAX<<2];
int cmp(Node a, Node b)
{
    return a.h<b.h;
}
struct Tree
{
    int l, r;
    int cnt;
    double len;
}tree[MAX<<2];
int findPos(int l, int r, double val)
{
    int mid;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(x[mid]>val)
            r=mid-1;
        else if(x[mid]<val)
            l=mid+1;
        else
            break;
    }
    return mid;
}
void build(int rt, int left, int right)
{
    tree[rt].l=left;
    tree[rt].r=right;
    tree[rt].len=0;
    tree[rt].cnt=0;
    if(left==right)
        return ;
    int mid=(left+right)>>1;
    build(rt<<1, left, mid);
    build(rt<<1|1, mid+1, right);
}
void pushUp(int rt)
{
    if(tree[rt].cnt)//非0,整段覆盖
        tree[rt].len=x[tree[rt].r+1]-x[tree[rt].l];
    else if(tree[rt].l==tree[rt].r)//叶子
        tree[rt].len=0;
    else//部分覆盖
        tree[rt].len=tree[rt<<1].len+tree[rt<<1|1].len;
}
void update(int rt, int left, int right, int val)
{
    if(left<=tree[rt].l && tree[rt].r<=right)//全部包含
    {
        tree[rt].cnt+=val;
        pushUp(rt);
        return ;
    }
    int mid=(tree[rt].l+tree[rt].r)>>1;
    if(left<=mid)
        update(rt<<1, left, right, val);
    if(right>mid)
        update(rt<<1|1, left, right, val);
    pushUp(rt);//计算该区间被覆盖的总长度
}
int main()
{
    int K=0;
    int l, r;
    double x1, x2, y1, y2;
    while(~scanf("%d", &N), N)
    {
        int cnt=0;
        for(int i=1; i<=N; i++)
        {
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            x[++cnt]=x1;
            node[cnt].l=x1;
            node[cnt].r=x2;
            node[cnt].h=y1;
            node[cnt].flag=1;//下边
            x[++cnt]=x2;
            node[cnt].l=x1;
            node[cnt].r=x2;
            node[cnt].h=y2;
            node[cnt].flag=-1;//上边
        }
        sort(x+1, x+cnt+1);//排序
        sort(node+1, node+cnt+1, cmp);
        /*
        int M=1;
        for(int i=1; i<cnt; i++)//去重  可去可不去
            if(x[i]!=x[i-1])
                x[M++]=x[i];
        */
        build(1, 1, cnt);
        double ans=0;
        for(int i=1; i<=cnt; i++)//拿出每条横线并且更新
        {
            l=findPos(1, cnt, node[i].l);
            r=findPos(1, cnt, node[i].r)-1;
            update(1, l, r, node[i].flag);
            ans+=tree[1].len*(node[i+1].h-node[i].h);//求面积
        }
        printf("Test case #%d\nTotal explored area: %.2f\n\n", ++K, ans);
    }
    return 0;
}






      二维线段树 (区间交) 查询两次的面积    点击打开链接  难题

代码如下:

#include<iostream>
#include<cstring>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int MAX=2010;
int T, N, cnt;
double x[MAX<<2];
struct Node
{
    double l, r;
    double y;
    double flag;
}node[MAX<<2];
int cmp(Node a ,Node b)
{
    return a.y<b.y;
}
struct Tree
{
    int l, r;
    double add;
    double len1, len2;
}tree[MAX<<2];

int findPos(double val)//找val在x[]中出现的位置
{
    int l=1, r=cnt;
    int mid;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(x[mid]<val)
            l=mid+1;
        else if(x[mid]>val)
            r=mid-1;
        else
            break;
    }
    return mid;
}

void build(int rt, int left, int right)
{
    tree[rt].l=left;
    tree[rt].r=right;
    tree[rt].add=tree[rt].len1=tree[rt].len2=0;//全置0
    if(left==right)//叶子
    {
        return ;
    }
    int mid=(left+right)>>1;
    build(rt<<1, left, mid);
    build(rt<<1|1, mid+1, right);
}
void pushUp(int rt)
{
    if(tree[rt].add>=2)
    {
        tree[rt].len1=tree[rt].len2=x[tree[rt].r+1]-x[tree[rt].l];
    }
    else if(tree[rt].add==1)
    {
        tree[rt].len1=x[tree[rt].r+1]-x[tree[rt].l];
        if(tree[rt].l==tree[rt].r)
            tree[rt].len2=0;
        else
            tree[rt].len2=tree[rt<<1].len1+tree[rt<<1|1].len1;
    }
    else
    {
        if(tree[rt].l==tree[rt].r)
            tree[rt].len1=tree[rt].len2=0;
        else
        {
            tree[rt].len1=tree[rt<<1].len1+tree[rt<<1|1].len1;
            tree[rt].len2=tree[rt<<1].len2+tree[rt<<1|1].len2;
        }
    }
}
void update(int rt, int left, int right, int val)
{
    if(tree[rt].l==left && tree[rt].r==right)
    {
        tree[rt].add+=val;
        pushUp(rt);
        return ;
    }
    int mid=(tree[rt].l+tree[rt].r)>>1;
    if(right<=mid)
        update(rt<<1, left, right, val);
    else if(left>mid)
        update(rt<<1|1, left, right, val);
    else
    {
        update(rt<<1, left, mid, val);
        update(rt<<1|1, mid+1, right, val);
    }
    pushUp(rt);
}
int main()
{
    double x1, x2, y1, y2;
    int l, r;
    cin>>T;
    while(T--)
    {
        cnt=0;
        memset(node, 0, sizeof(node));
        memset(tree, 0, sizeof(tree));
        cin>>N;
        for(int i=1; i<=N; i++)
        {
            cin>>x1>>y1>>x2>>y2;
            x[++cnt]=x1;
            node[cnt].l=x1;
            node[cnt].r=x2;
            node[cnt].y=y1;
            node[cnt].flag=1;//左边
            x[++cnt]=x2;
            node[cnt].l=x1;
            node[cnt].r=x2;
            node[cnt].y=y2;
            node[cnt].flag=-1;//右边
        }
        //cout<<"..........   cnt=  "<<cnt<<endl;
        sort(x+1, x+cnt+1);
        build(1, 1, cnt);
        sort(node+1, node+cnt+1, cmp);
        double ans=0;
        l=findPos(node[1].l);
        r=findPos(node[1].r)-1;
        update(1, l, r, node[1].flag);
        for(int i=2; i<=2*N; i++)
        {
            ans+=tree[1].len2*(node[i].y-node[i-1].y);
            l=findPos(node[i].l);
            r=findPos(node[i].r)-1;
            update(1, l, r, node[i].flag);
        }
        printf("%.2lf\n", ans);
        //cout<<fixed<<setprecision(2)<<ans<<endl;//输出位数不准确
    }
    return 0;
}


      二维线段树  求固定面积矩形(W*H)内星星和的最大值   点击打开链接

代码如下:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int MAX=20010;
typedef long long LL;
LL xis[MAX<<2];
struct Node
{
    LL l, r;
    LL h;
    LL s;
    bool operator <(const Node & a) const
    {
        if(h==a.h)
            return s<a.s;
        return h<a.h;
    }
}node[MAX];
struct Tree
{
    int l, r;
    LL cnt;
    LL sum;
}tree[MAX<<2];
int findPos(int l, int r, LL val)
{
    int mid;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(xis[mid]>val)
            r=mid-1;
        else if(xis[mid]<val)
            l=mid+1;
        else
            break;
    }
    return mid;
}
void pushUp(int rt)
{
    tree[rt].sum=max(tree[rt<<1].sum, tree[rt<<1|1].sum);
}
void pushDown(int rt)
{
    if(tree[rt].cnt)
    {
        tree[rt<<1].sum+=tree[rt].cnt;
        tree[rt<<1].cnt+=tree[rt].cnt;
        tree[rt<<1|1].sum+=tree[rt].cnt;
        tree[rt<<1|1].cnt+=tree[rt].cnt;
        tree[rt].cnt=0;
    }
}
void build(int rt, int left, int right)
{
    tree[rt].l=left;
    tree[rt].r=right;
    tree[rt].sum=tree[rt].cnt=0;
    if(left==right)
        return ;
    int mid=(left+right)>>1;
    build(rt<<1, left, mid);
    build(rt<<1|1, mid+1, right);
}
void update(int L, int R, int c, int rt)
{
    if(L<=tree[rt].l && tree[rt].r<=R)
    {
        tree[rt].cnt+=c;
        tree[rt].sum+=c;
        return ;
    }
    pushDown(rt);
    int mid=(tree[rt].l+tree[rt].r)>>1;
    if(L<=mid)
        update(L, R, c, rt<<1);
    if(mid<R)
        update(L, R, c, rt<<1|1);
    pushUp(rt);
}
int main()
{
    int N, W, H;
    while(cin>>N>>W>>H && N && W && H)
    {
        int m=0;
        LL ans=-1;
        LL x, y, c;
        int l, r;
        for(int i=1; i<=N; i++)
        {
            cin>>x>>y>>c;
            xis[++m]=x;
            node[m].l=x;
            node[m].r=x+W;
            node[m].h=y;
            node[m].s=c;
            xis[++m]=x+W;
            node[m].l=x;
            node[m].r=x+W;
            node[m].h=y+H;
            node[m].s=-c;
        }
        build(1, 1, m);
        sort(xis+1, xis+m+1);
        sort(node+1, node+m+1);
        for(int i=1; i<=m; i++)
        {
            l=findPos(1, m, node[i].l);
            r=findPos(1, m, node[i].r)-1;
            if(l<=r)
                update(l, r, node[i].s, 1);
            ans=max(ans, tree[1].sum);
        }
        cout<<ans<<endl;
    }
    return 0;
}



评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值