线段树+扫描线

看了一天才明白了

参考博客:https://blog.csdn.net/otowa/article/details/50695401

看完参考博客在看看我对这个的理解。

我的理解:扫描线是一种想法,方便我们去理解,线段的作用:维护我们所需要的值。

比我们要求上图的面积,我们可以将图形看成上图的形式(同种颜色我们求一次面积,最后相加)

我们所说的扫描线其实说的就是平行于X轴的边(看你自己理解,就是看你从下往上扫描,还是从左往右扫描)

那我们取求面积的时候怎么求解呢?

我们可以先把所有的扫描线存下来,然后升序排列  扫描线的参数(l,r,h,flag)

把所有横坐标存下来,然后离散化(因为坐标为double类型,我们建树需要用到int型)

我们求面积 = 相邻扫描线的高度差 (排序后很容易得到) × 扫描线中间有效长度(线段树维护的值,即有颜色的区域长度)

我们怎么用线段树维护我们想要的值呢?

我们用点表示区间来维护长度,可以看下图理解

我们用①表示 1-2之间的有效长度,⑤表示5-6之间的有效长度。

所以我们建树就是上图的表示方式,最终①—⑤就我们所需要的总的有效长度。

我们在更新树的时候只需要pushup就可以了,因为我们只需要①—⑤的有效长度,不需要他的儿子的有效长度

我们在pushup分为三种情况

①:如果这个区间被标记了,并且标记>1,说明这个区间被全部覆盖。

②:这个区间为树的叶子节点,并且没有被标记,说明这个区间没有被覆盖(有效长度为0)

③: 节点不是叶子节点但是没有被标记(可能是完全没有覆盖,可能是被覆盖了一些),他的有效长度为两个儿子的有效长度之和。

模板题:HDU1542

代码:

///#include<bits/stdc++.h>
///#include<unordered_map>
///#include<unordered_set>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<bitset>
#include<set>
#include<stack>
#include<map>
#include<list>
#include<new>
#include<vector>
#define MT(a,b) memset(a,b,sizeof(a));
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double pai=acos(-1.0);
const double E=2.718281828459;
const int MAXN=1e5;
const ll mod=1e9+7;
const int INF=0x3f3f3f3f;

int sign=0;
double x[205];

struct node
{
    double l;
    double r;
    double h;
    int flag;
    bool friend operator<(node a,node b)
    {
        return a.h<b.h;
    }
} line[205];

struct xx
{
    int s;
    double len;
} root[805];


void pushup(int sign,int l,int r)
{
    if(root[sign].s)        ///区间被全部覆盖
        root[sign].len=x[r+1]-x[l];
    else if(l==r)           ///没有被覆盖,并且是一个点
        root[sign].len=0;
    else        ///是一条没有整个区间被覆盖的线段,合并左右子的信息
        root[sign].len=root[sign<<1].len+root[sign<<1|1].len;
}

void updateroot(int sign,int l,int r,int a,int b,int f)
{
    if(l==a&&r==b)
    {
        root[sign].s+=f;
        pushup(sign,l,r);
        return ;
    }
    int mid=(l+r)>>1;
    if(b<=mid)
        updateroot(sign<<1,l,mid,a,b,f);
    else if(a>mid)
        updateroot(sign<<1|1,mid+1,r,a,b,f);
    else
    {
        updateroot(sign<<1,l,mid,a,mid,f);
        updateroot(sign<<1|1,mid+1,r,mid+1,b,f);
    }
    pushup(sign,l,r);
}
int main()      ///从下往上扫描
{
    int n,u=0;
    double x1,y1,x2,y2;
    while(scanf("%d",&n)!=EOF,n)
    {
        int sign=0;
        memset(root,0,sizeof(root));    ///树清空,也可以自己建一棵空树
        for(int i=1; i<=n; i++)         ///记录扫描线和横坐标
        {
            scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
            line[++sign]=node{x1,x2,y1,1};
            x[sign]=x1;
            line[++sign]=node{x1,x2,y2,-1};
            x[sign]=x2;
        }
        sort(line+1,line+1+sign);
        sort(x+1,x+1+sign);
        int d=unique(x+1,x+1+sign)-(x+1);   ///对横坐标去重,离散化
        double ans=0.0;
        for(int i=1; i<sign; i++)
        {
            int a=lower_bound(x+1,x+1+d,line[i].l)-x;   
            int b=lower_bound(x+1,x+1+d,line[i].r)-x-1; ///因为用点表示区间,矩形的右端点属于r-1的这个矩形的
            updateroot(1,1,d-1,a,b,line[i].flag);   ///更新,总共d个点,只有d-1个区间
            ans+=root[1].len*(line[i+1].h-line[i].h);
        }
        printf("Test case #%d\n",++u);
        printf("Total explored area: %.2f\n\n",ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42211531/article/details/86563517