线段树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("\n");
    }
    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;
} 

 

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

 

 

 

 

转载于:https://www.cnblogs.com/Robin20050901/p/9876321.html

### 权值线段树中的标记概念 标记(Lazy Tag)是一种用于优化区间操作的技术,广泛应用于线段树的数据结构中。其核心思想是在执行区间更新时,不立即逐个修改区间的每一个元素,而是在父节点上记录该次更新的信息,并推迟到真正需要访问这些子节点时才进行实际的修改[^3]。 #### 标记的作用 当需要对某个区间的所有元素进行相同的修改操作(如加法、乘法或其他统一的操作),如果直接遍历并逐一修改每个元素,则时间复杂度可能达到 \(O(n)\),这在大规模数据下效率较低。通过引入标记机制,可以在常数时间内完成一次区间更新操作,从而显著提高性能[^4]。 #### 标记的具体实现 以下是标记线段树中的基本实现方式: 1. **增加辅助数组 `lazy[]`** 定义一个额外的数组 `lazy[]`,用来存储尚未传递给子节点的延迟更新信息。初始状态下,所有 `lazy[i]` 的值均为默认值(通常为0表示无待处理更新)。 2. **构建线段树** 构建过程与普通的线段树相同,但在初始化阶段需确保所有的标记都设置为空状态。 3. **查询前先下传标记** 在每次向下递归之前,检查当前节点是否有未处理的标记。如果有,则将其影响施加于两个子节点对应的区间范围,并清除当前节点上的标记。 4. **更新操作** 当需要对某一段区间 `[L,R]` 进行批量更新时,找到覆盖此区间的最小数量的连续节点集合,在这些节点处打上相应的标记即可。 下面是一个简单的 Python 实现例子展示如何使用标记来支持快速区间求和以及单点查询功能: ```python class LazySegmentTree: def __init__(self, n): self.n = n self.tree = [0] * (4*n) self.lazy = [0] * (4*n) def push_down(self, rt, ln, rn): if self.lazy[rt]: self.tree[rt*2] += self.lazy[rt]*ln self.tree[rt*2+1] += self.lazy[rt]*rn self.lazy[rt*2] += self.lazy[rt] self.lazy[rt*2+1] += self.lazy[rt] self.lazy[rt] = 0 def update_range(self, l, r, val, L=0, R=None, rt=1): if R is None: R=self.n-1 if l<=L and R<=r: self.tree[rt] += (R-L+1)*val self.lazy[rt] += val return mid = (L+R)//2 self.push_down(rt, mid-L+1, R-mid) if l <= mid: self.update_range(l, r, val, L=L, R=mid, rt=rt<<1) if r > mid: self.update_range(l, r, val, L=mid+1, R=R, rt=(rt<<1)|1) self.tree[rt] = self.tree[rt*2]+self.tree[rt*2+1] def query_sum(self, l, r, L=0, R=None, rt=1): if R is None: R=self.n-1 if l<=L and R<=r: return self.tree[rt] mid = (L+R)//2 self.push_down(rt, mid-L+1, R-mid) res = 0 if l <= mid: res += self.query_sum(l, r, L=L, R=mid, rt=rt<<1) if r > mid: res += self.query_sum(l, r, L=mid+1, R=R, rt=(rt<<1)|1) return res ``` 上述代码展示了基于标记技术的一个典型应用场景——动态维护序列的部分和问题解决方案之一部分。 #### 总结 标记极大地提升了线段树应对大量重复性区域变更请求的能力,使得原本耗时巨大的任务得以高效完成。这种技巧不仅限于数值型运算场景,在字符串匹配等领域也有着广泛应用前景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值