线段树初步

Posted CaptainLi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树初步相关的知识,希望对你有一定的参考价值。

今天我们学习扫描线。扫描线大多数时候用于计算矩形面积,那我们如何处理有重合的情况呢?

我们想象有一条线从下向上扫描,之后每次扫描的时候,你可以计算一下当前在线上有多长的区间被覆盖,我们相当于把矩形转化为无数小段,之后分别进行区间求和。这一部分是可以用线段树实现的。

这里有一些问题,首先因为有重叠的区间,所以我们需要记录每个区间被覆盖过多少次,这样可以避免在删除边的时候计算面积出错。我们可以在一开始对边赋值,入边为1,出边为-1,每次进行区间修改的时候相应的修改每个区间的值。如果区间当前的值>=1,就说明当前区间已经被完全覆盖,如果是0则没有被完全覆盖。注意在每次返回和修改的时候都要先修改区间值,在依据相应的情况修改区间总和。

如何优化呢?在每条两条线之间是没有变化的,所以这部分的矩形可以直接用width*length的方法计算出面积之后加上即可。这样只要枚举每一条线就好了。

还有就是要注意因为计算的是区间,所以每次传参要区间首++。

最后就是这个题与普通区间修改不同,他不需要lazy,他也不需要下放任何标记,因为你如果不需要修改子区间的话他就不需要下放,而如果你修改了已经覆盖的区间之中的一小段区间,那么对于大区间并没有任何影响。

总的来说扫描线的基本应用还是很简单的,不过细节很多……

看一下题吧!

题目描述

在2051年,若干火星探险队探索了这颗红色行星的不同的区域并且制作了这些区域的地图。

现在, Baltic空间机构有一个雄心勃勃的计划:他们想制作一张整个行星的地图。为了考虑必要的工作,他们需要知道地图上已经存在的全部区域的大小。你的任务是写一个计算这个区域大小的程序。

任务:计算地图覆盖的全部的区域。

输入输出格式

输入格式:

 

输入文件的第一行包含一个整数N(1<=N<=10000),表示可得到的地图数目。

以下N行,每行描述一张地图。

每行包含4个整数x1,y1,x2和y2(0<=x1<x2<=30000,0<=y1<y2<=30 000)。数值(x1,y1)和(x2,y2)是坐标,分别表示绘制区域的左上角和右下角坐标。每张地图是矩形的,并且它的边是平行与X坐标轴或Y坐标轴的。

 

输出格式:

 

包含一个整数,表示探索区域的总面积(即所有矩形的公共面积)。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#define rep(i,a,n) for(ll i = a;i <= n;i++)
#define per(i,n,a) for(ll i = n;i >= a;i--)
#define enter putchar(‘\n‘)

using namespace std;
typedef long long ll;
const ll M = 300005;

struct seg
{
    ll v,w;
}t[M*4];
ll n,ans,cnt,a,b,c,d;
struct lines
{
    ll begin,end,high,x;
}l[M];
bool cmp(lines a,lines b)
{
    return a.high < b.high;
}
ll read()
{
    ll ans = 0,op = 1;
    char ch = getchar();
    while(ch < 0 || ch > 9)
    {
        if(ch == -) op = -1;
        ch = getchar();
    }
    while(ch >= 0 && ch <= 9)
    {
        ans *= 10;
        ans += ch - 0;
        ch = getchar();
    }
    return ans * op;
}

void build(ll p,ll l,ll r)
{
    t[p].v = t[p].w = 0;
    if(l == r) return;
    ll mid = (l+r) >> 1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
}

void modify(ll p,ll l,ll r,ll kl,ll kr,ll q)
{
    if(l == kl && r == kr)
    {
        t[p].w += q;
        if(t[p].w == 0) t[p].v = t[p<<1].v + t[p<<1|1].v;
        else t[p].v = r-l+1;
//        else t[p].v = 0;
        return;
    }
    ll mid = (l+r) >> 1;
    if(kr <= mid) modify(p<<1,l,mid,kl,kr,q);
    else if(kl > mid) modify(p<<1|1,mid+1,r,kl,kr,q);
    else modify(p<<1,l,mid,kl,mid,q),modify(p<<1|1,mid+1,r,mid+1,kr,q);
    if(t[p].w == 0) t[p].v = t[p<<1].v + t[p<<1|1].v;
    else t[p].v = r-l+1;
}

int main()
{
    n = read();
    build(1,0,M);
    rep(i,1,n)
    {
        a = read(),b = read(),c = read(),d = read();
        l[++cnt].begin = a,l[cnt].end = c,l[cnt].high = b,l[cnt].x = 1;
        l[++cnt].begin = a,l[cnt].end = c,l[cnt].high = d,l[cnt].x = -1;
    }
    sort(l+1,l+1+cnt,cmp);
//    rep(i,1,cnt) prllf("%d %d %d %d\n",l[i].begin,l[i].end,l[i].high,l[i].x);
    rep(i,1,cnt)
    {
        ll h = l[i].high - l[i - 1].high;
        ans += t[1].v * h;
        modify(1,0,M,l[i].begin+1,l[i].end,l[i].x);
//        printf("%d\n",t[1].v);
    }
    printf("%lld\n",ans);
    return 0;
}

 

以上是关于线段树初步的主要内容,如果未能解决你的问题,请参考以下文章

线段树初步&&lazy标记

线段树初步

主席树初步

关于线段树的初步理解

线段树详解

可持久化线段树(待补充)