写个扫描线吧

发布时间 2023-09-21 22:42:56作者: HL_ZZP

虽然扫描线很简单,但是我发现理解还是有些不清晰,于是决定写一篇。
原问题,或者说是例题

一个不好暴力东西,暴力模拟不带优化至少O(n^3)
也是一个非常经典的线段树例题

(因为懒得画图所以下面就直接用网上dalao的题解里面的图片了)
首先,因为我们要在线段树上面记录下来每一个起始点和结束点,所以离散化是必须的

下面是一个O(n^2)暴力
我们可以建立一个c[i]数组,里面是i这个点被覆盖的长度,然后用一个len数组来记录一下一共有多少地方被覆盖过
这个len是用线段树维护的
到这里就比较明显了,对于这个c[i],我们离散化之后其实就是对它进行一个区间修改和区间查询,那就是线段是的例题了。
最终就是一个O(nlogn)

 

然后一些细节,因为这个题目算是加深了我对于lazytag的理解
我写的时候刚开始没有感觉,然后仔细想想发现对于这个len我没法用lazytag维护(这个其实就是对于lazytag的理解
lazytag是在每一次查询的时候发挥作用,如果这个区间刚刚好完全被包含在内,那么就不需要继续往下递归,而是直接返还已经算好的值
而对于不是完全被包含的区间,则是在递归前把它的祖宗节点的lazytag传给他的儿子节点,这样lazytag就成功的被处理了。
而这个为什么不能用lazytag就是因为这个,我们要记录的内容是:这个区间里面有几个点被标记过
但是,对于一个区间,它被lazytag标记前我们不知道里面有几个节点被标记了,所以我们也不知道里面有几个点会在被改变后给len贡献值(就是不知道有几个点会从0变成1)。
lazytag能做的事情有一个特点:他能够在一个区间的层面上统计一个修改能够对我们需要的答案造成什么影响。
就比如,lazytag能用在区间加和区间查询数字和上,因为一个区间加上一个数字我们可以用这个数字*区间长度来计算他们的和的变化。
但是这个不行。
所以我们对于每一次修改都要从一个我们能统计的地方开始统计有几个点被覆盖过,然后把答案上传,汇在一起。
这个过程就直接做就好了,大概就是 如果有一个区间,他们是集体有值的,那就给这个区间的len+=r-l+1,如果不是,那就让这个点的len等于它左儿子的len+右儿子的len
这也是我线段树经常想糊涂的地方。
希望我和看这篇博客的人现在和以后都不会再想不清楚了吧

然后这个题目有一个要注意的点就是,我们线段树上面的每一个点,其实对应的是一个区间,而不是一个点,所以写的时候有些部分就要变化

##代码

 

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read() {
    char c=getchar();ll a=0,b=1;
    for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
    for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
ll n,totx,disx[4000001],disy[4000001],toty,sum[4000001];
ll disx2[4000001],totx2,disy2[4000001],toty2,ans,len[4000001];
struct node
{
    ll x,y,a,b,k;
}e[4000001],e1[4000001];
bool cmp(node a,node b)
{
    return a.x<b.x;
}
ll ery(ll x)//没用 
{
    ll l=1,r=toty2;
    while(l<=r)
    {
        ll mid=(l+r)/2;
        if(disy2[mid]==x)return mid;
        if(disy2[mid]>x)r=mid-1;
        else l=mid+1;
    }
    return l;
}
inline ll ls(ll x){
    return x*2;
}
inline ll rs(ll x){
    return x*2+1;
}
inline void push_up(ll p,ll l,ll r)
{
    if(sum[p])
        len[p]=disy2[r+1]-disy2[l];
    else 
        len[p]=len[ls(p)]+len[rs(p)];
}
void build(ll l,ll r,ll p)
{
    sum[p]=0;len[p]=0;
    if(l==r)return;
    ll mid=(l+r)/2;
    build(l,mid,ls(p));
    build(mid+1,r,rs(p));
    push_up(p,l,r);
}
void add(ll tl,ll tr,ll l,ll r,ll p,ll k)
{
    if(r+1<=tl||tr<=l)return;
    if(tl<=l&&tr>=r+1)
    {
        sum[p]+=k;
        push_up(p,l,r);
        return;
    }
    ll mid=(l+r)/2;
    if(tl<=mid)add(tl,tr,l,mid,ls(p),k);
    if(mid<tr)add(tl,tr,mid+1,r,rs(p),k);
    push_up(p,l,r);
}
int main()
{
    n=read();
    for(ll i=1;i<=n;i++)
    {
        e[i].x=read();e[i].y=read();
        e[i].a=read();e[i].b=read();
        e1[i].x=e[i].x;  e1[i].y=e[i].y;  e1[i].b=e[i].b;  e1[i].k=1;
        e1[i+n].x=e[i].a;e1[i+n].y=e[i].y;e1[i+n].b=e[i].b;e1[i+n].k=-1;
        disy[++toty]=e[i].y;//x的离散化没用 
        disy[++toty]=e[i].b;
    }
    sort(disy+1,disy+toty+1);disy[0]=-1;
    for(ll i=1;i<=toty;i++)
        if(disy[i]!=disy[i-1])
            disy2[++toty2]=disy[i];//离散化 
    sort(e1+1,e1+1+n*2,cmp);
    build(1,toty2,1);
    for(ll i=1;i<n*2;i++)
    {
        add(ery(e1[i].y),ery(e1[i].b),1,toty2,1,e1[i].k);
        ans+=len[1]*(e1[i+1].x-e1[i].x);
    }
    cout<<ans<<endl;
    return 0;
}

 

下面是poj2482,一个扫描线的应用

代码