线段树2——懒标记

Posted robin20050901

tags:

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

学完了线段树的基础知识(建树,修改,查询等操作)后,来看看下面这题

技术分享图片

注:原题链接:https://www.luogu.org/problemnew/show/P3372

读完这题,是不是有跃跃欲试的感觉?

1)查询区间和   简单

2)区间修改??? 

for(int i=l;i<=r;i++)add(1,i,val);

所以,智障的我就写出了这样的代码

然后,稳稳的TLE

我们来分析一下上述代码的时间复杂度

1. 查询操作 O ( nlogn )

2. 区间修改 每一次单点修改O(n),总共n次,O(nlogn)

技术分享图片

 

100000*100000*log(100000)  TLE!!!

所以,这时候,我们需要——

懒标记


1.懒标记用途

懒标记的作用是,在区间修改的时候,我们在需要修改的区间放个标记,下次要用的时候再打开。

这样可以节省大量的时间,可以把区间修改的时间复杂度降至O(log(n))

2.懒标记的实现

上述做法固然解决了区间修改的问题

但是——

我们要查询的时候怎么办,总不能

cout<<"I can‘t do it"<<endl;

 

我们需要——

把懒标记拆了

 

if(tree[root].l==l&&tree[root].r==r)// 找到区间
        return tree[root].val+tree[root].tag*(r-l+1);// 计算并返回答案
if(tree[root].tag)//如果有懒标记
{
    tree[root*2].tag+=tree[root].tag; 
    tree[root*2+1].tag+=tree[root].tag;//下传懒标记
    tree[root].val+=(tree[root].r-tree[root].l+1)*tree[root].tag;//计算答案
    tree[root].tag=0;//清空标记
}

 

这样,我们就完成了查询和清除懒标记的工作。

回过头谈谈修改

void add(int root,int l,int r,int val)
{
    if(tree[root].l==l&&tree[root].r==r)
    {
        tree[root].tag+=val;
        return;
    }
    tree[root].val+=(r-l+1)*val;
    if(tree[root].mid>=r)add(root*2,l,r,val);
    else if(tree[root].mid<l)add(root*2+1,l,r,val);
    else add(root*2,l,tree[root].mid,val),add(root*2+1,tree[root].mid+1,r,val);
}

找到区间放标记,回头再来查答案

3.线段树经典题目

luogu P1886 滑动窗口

其实,这题是单调队列的题目

但用线段树依旧能过(只要你的线段树常数不大)

代码:

 

// luogu-judger-enable-o2
#include<bits/stdc++.h>

using namespace std;

int n,k;
struct T{
    int l,r,mid,v;
}t1[4000005],t2[4000005];

void build(int root,int l,int r)
{
    int mid=(l+r)/2;
    t1[root].l=l,t1[root].r=r,t1[root].mid=mid;
    t2[root].l=l,t2[root].r=r,t2[root].mid=mid;
    if(l==r)
    {
        scanf("%d",&t1[root].v);
        t2[root].v=t1[root].v;
        return;
    }
    build(root*2,l,mid);
    build(root*2+1,mid+1,r);
    t1[root].v=max(t1[root*2].v,t1[root*2+1].v);
    t2[root].v=min(t2[root*2].v,t2[root*2+1].v); 
}

int query(int a,int b,int root,int flag)
{
    int l=t1[root].l,r=t1[root].r;
    int mid=(l+r)/2;
    if(a==l&&b==r)return flag==1?t1[root].v:t2[root].v;
    if(b<=mid)return query(a,b,root*2,flag);
    else if(a>mid)return query(a,b,root*2+1,flag);
    else return flag==1?max(query(a,mid,root*2,flag),query(mid+1,b,root*2+1,flag)):min(query(a,mid,root*2,flag),query(mid+1,b,root*2+1,flag));    
}

int main()
{
    scanf("%d%d",&n,&k);
    build(1,1,n);
    for(int f=0;f<=1;f++)
    { 
        for(int i=1;i<=n-k+1;i++)
            printf("%d ",query(i,i+k-1,1,f));
        printf("
");
    }
    return 0;
}

 

简单的线段树练手题。

顺便说下单调队列的方法

 

#include<bits/stdc++.h>

#define top front

using namespace std;

int n,k;
int a[1000005];
deque<int>q;

int main()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++)cin>>a[i]; 
    for(int i=1;i<=k;i++)
    {
        while(!q.empty()&&a[q.back()]>a[i])q.pop_back();
        q.push_back(i);
    }
    cout<<a[q.front()]<< ;
    for(int i=k+1;i<=n;i++)
    {
        while(!q.empty()&&a[q.back()]>a[i])q.pop_back();
        q.push_back(i);
        while(!q.empty()&&q.front()<=i-k)q.pop_front();
        cout<<a[q.front()]<< ;
    }
    cout<<endl;
    while(!q.empty())q.pop_front();
    for(int i=1;i<=k;i++)
    {
        while(!q.empty()&&a[q.back()]<a[i])q.pop_back();
        q.push_back(i);
    }
    cout<<a[q.front()]<< ;
    for(int i=k+1;i<=n;i++)
    {
        while(!q.empty()&&a[q.back()]<a[i])q.pop_back();
        q.push_back(i);
        while(!q.empty()&&q.front()<=i-k)q.pop_front();
        cout<<a[q.front()]<< ;
    }
    cout<<endl;
    
    
    return 0;
} 

 

好了,学完了懒标记,遇到题目直接用线段树吧!

 

 

 

 


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

[P2894][USACO08FEB] 酒店Hotel (线段树+懒标记下传)

线段树 懒标记 区间加一个简单的整数问题2

[模板]洛谷T3373 线段树 模板2

「ZJOI2019」线段树

线段树

线段树区间修改区间求和