首页 > 其他分享 >线段树(2)——懒惰标记Lazy Tag(单运算)及例题

线段树(2)——懒惰标记Lazy Tag(单运算)及例题

时间:2024-08-23 20:38:21浏览次数:7  
标签:Lazy now int tr lazy mid tl Tag 例题

上一篇文章我们讲了线段树的最基本的操作。如果有一种操作叫做区间加法呢?这个时候显然可以依次单点修改,但是时间复杂度太高了。所以可以考虑优化,由于思考过程可能很长,此处直接引入懒惰标记。

懒惰标记就是在对一颗树的所有节点进行某种统一操作时,只对根节点做一个标记表示它的子树都要进行这个操作,但是懒惰标记仅可用于能够计算出若对树上的每个节点进行操作时,能够通过根节点直接算出查询值。这可能有些复杂,举个例子,上次的题目有一道需要区间开方根,查询的是和,对一个树开方根然后求和,这是和其子树上每一个点的值有关的,因此不能使用懒惰标记。而区间加减,只需要通过树的和加上操作数乘以子树大小即可。区间乘法,区间赋值都是可以的。

讲了这么多没用的,看看懒惰标记是怎么实现的吧。比如给区间 \([2,5]\) 同时加上 \(x\),那么假设数组总共有 \(5\) 个元素,那么线段树如下(其实就是不停的用同一个而以惹):

那么到 \([4,5]\) 的时候,很显然是完全包含的,所以给 \([4,5]\) 的懒惰标记 加上 \(x\),因为懒惰标记是有可能叠加的。左边的分别是到叶子节点 \(2\) 和 \(3\),这个时候应该如何处理呢?其实是不用特判叶子的,因为叶子即使用懒惰标记,也不会影响,这个懒惰标记存在是合理的,如果不存在,也不会影响。那么现在要访问 \([4,4]\) 的和,遍历中会遇到有懒惰标记的节点的子结点,这个时候如果还保持懒惰标记不变显然就没有什么意义了,而且访问到 \(4\) 的值是区间增加前的。所以当需要访问有懒惰标记的结点的子结点的信息时,需要使用下放操作。下放操作即将懒惰标记的信息发放到子结点,由于懒惰标记保存的是 \(x\) 而不是 \(x \times (tr-tl+1)\),所以只需要将左右子结点的懒惰标记加上 \(lazy_now\),线段树的值加上:左子结点加上 \(lazy_now \times (mid-tl+1)\);右子结点加上 \(lazy_now \times (tr-mid)\)。然后清空 \(lazy_now\)。代码如下,设下放操作为函数 \(push_down\):

void push_down(int now,int tl,int tr){
    int mid=(tl+tr)/2;
    t[now*2]+=lazy[now]*(mid-tl+1);
    t[now*2+1]+=lazy[now]*(tr-mid);
    lazy[now*2]+=lazy[now];
    lazy[now*2+1]+=lazy[now];
    lazy[now]=0;
}

在修改和查询操作的时候,需要在判断区间在需要操作的区间完全包括内或完全不包括内之后即不完全包括时,需要加入下放操作即:

if(tl>=l&&tr<=r){
    ...
}
if(tl>r||tr<l){
    ...
}
push_down(now,tl,tr);

完整代码为:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,a[N],t[N*4],lazy[N*4];
//push_down:下放操作
void push_down(int now,int tl,int tr){
    int mid=(tl+tr)/2;
    t[now*2]+=lazy[now]*(mid-tl+1);
    t[now*2+1]+=lazy[now]*(tr-mid);
    lazy[now*2]+=lazy[now];
    lazy[now*2+1]+=lazy[now];
    lazy[now]=0;
}
//build:建树
void build(int now,int tl,int tr){
    if(tl==tr){
        t[now]=a[tl];
        return ;
    }
    int mid=(tl+tr)/2;
    build(now*2,tl,mid);
    build(now*2+1,mid+1,tr);
    t[now]=t[now*2]+t[now*2+1];
}
//add:now表示当前结点,tl和tr表示线段树的区间,l和r表示需要增加数值的区间,x表示增加的值
void add(int now,int tl,int tr,int l,int r,int x){
    if(tl>=l&&tr<=r){
        t[now]+=x*(tr-tl+1);
        lazy[now]+=x;
        return ;
    }
    if(tl>r||tr<l){
        return ;
    }
    if(lazy[now]){
        push_down(now,tl,tr);
    }
    int mid=(tl+tr)/2;
    add(now*2,tl,mid,l,r,x);
    add(now*2+1,mid+1,tr,l,r,x);
    t[now]=t[now*2]+t[now*2+1];
}
//query:查询
int query(int now,int tl,int tr,int l,int r){
    if(tl>=l&&tr<=r){
        return t[now];
    }
    if(tl>r||tr<l){
        return 0;
    }
    if(lazy[now]){
        push_down(now,tl,tr);
    }
    int mid=(tl+tr)/2;
    return query(now*2,tl,mid,l,r)+query(now*2,mid+1,tr,l,r);
}
int main(){
    n=5;
    a[1]=1,a[2]=2,a[3]=3,a[4]=4,a[5]=5;
    build(1,1,n);
    add(1,1,n,2,4,5);
    cout<<query(1,1,n,1,5);
	return 0;
}

区间乘法也可以仿制。注意,build的时候需要设置懒惰标记为 \(1\)。假设这里求区间和。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,a[N],t[N*4],multi[N*4];
void push_down(int now,int tl,int tr){
    int mid=(tl+tr)/2;
    t[now*2]+=multi[now]*(mid-tl+1);
    t[now*2+1]+=multi[now]*(tr-mid);
    multi[now*2]*=multi[now];
    multi[now*2+1]*=multi[now];
    multi[now]=1;
}
void build(int now,int tl,int tr){
    multi[now]=1;
    if(tl==tr){
        t[now]=a[tl];
        return ;
    }
    int mid=(tl+tr)/2;
    build(now*2,tl,mid);
    build(now*2+1,mid+1,tr);
    t[now]=t[now*2]+t[now*2+1];
}
void mul(int now,int tl,int tr,int l,int r,int x){
    if(tl>=l&&tr<=r){
        t[now]*=x;
        multi[now]+=x;
        return ;
    }
    if(tl>r||tr<l){
        return ;
    }
    if(multi[now]!=1){
        push_down(now,tl,tr);
    }
    int mid=(tl+tr)/2;
    mul(now*2,tl,mid,l,r,x);
    mul(now*2+1,mid+1,tr,l,r,x);
    t[now]=t[now*2]+t[now*2+1];
}
int query(int now,int tl,int tr,int l,int r){
    if(tl>=l&&tr<=r){
        return t[now];
    }
    if(tl>r||tr<l){
        return 0;
    }
    if(multi[now]!=1){
        push_down(now,tl,tr);
    }
    int mid=(tl+tr)/2;
    return query(now*2,tl,mid,l,r)+query(now*2+1,mid+1,tr,l,r);
}
int main(){
    n=5;
    a[1]=1,a[2]=2,a[3]=3,a[4]=4,a[5]=5;
    build(1,1,n);
    mul(1,1,n,2,4,5);
    cout<<query(1,1,n,1,5)<<" "<<query(1,1,n,4,4);
	return 0;
}

在做懒惰标记这类题目时,重点在于多思考,最好画个图模拟一下,看看每个地方是怎么改的,怎么下放的,思想很重要,代码也重要。线段树好写好调,对着模板多写几遍,就不容易出错了,一般在写大型题目时,建议先写线段树,然后弄几个简单的例子测一下线段树有没有写错再下一步写。

区间乘法,查询区间积;区间加法,查询区间积等都建议自己思考思考。一般来说CSP-J中线段树的题目真正应用的时候不会出的太难,所以仅仅是不会线段树想了解了解或者不打CSP-S的人可以止步这里了,接下来线段树(3)的内容要开始烧脑了。

例题

【模板】线段树 1

模板题。貌似要开long long。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+10;
ll n,m,a[N],t[N*4],lazy[N*4];
void build(ll now,ll tl,ll tr){
	if(tl==tr){
		t[now]=a[tl];
		return ;
	}
	ll mid=(tl+tr)/2;
	build(now*2,tl,mid);
	build(now*2+1,mid+1,tr);
	t[now]=t[now*2]+t[now*2+1];
}
void push_down(ll now,ll tl,ll tr){
	ll mid=(tl+tr)/2;
	t[now*2]+=lazy[now]*(mid-tl+1);
	t[now*2+1]+=lazy[now]*(tr-mid);
	lazy[now*2]+=lazy[now];
	lazy[now*2+1]+=lazy[now];
	lazy[now]=0;
}
ll query(ll now,ll tl,ll tr,ll l,ll r){
	if(tl>=l&&tr<=r){
		return t[now];
	}
	if(tl>r||tr<l){
		return 0;
	}
	if(lazy[now]){
		push_down(now,tl,tr);
	}
	ll mid=(tl+tr)/2;
	return query(now*2,tl,mid,l,r)+query(now*2+1,mid+1,tr,l,r);
}
void add(ll now,ll tl,ll tr,ll l,ll r,ll x){
	if(tl>=l&&tr<=r){
		lazy[now]+=x;
		t[now]+=x*(tr-tl+1);
		return ;
	}
	if(tl>r||tr<l){
		return ;
	}
	if(lazy[now]){
		push_down(now,tl,tr);
	}
	ll mid=(tl+tr)/2;
	add(now*2,tl,mid,l,r,x);
	add(now*2+1,mid+1,tr,l,r,x);
	t[now]=t[now*2]+t[now*2+1];
}
int main(){
	//freopen("xx.in","r",stdin);
	//freopen("xx.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m;
	for(ll i=1;i<=n;i++){
		cin>>a[i];
	}
	build(1,1,n);
	for(ll i=1;i<=m;i++){
		ll opt,x,y,k;
		cin>>opt>>x>>y;
		if(opt==1){
			cin>>k;
			add(1,1,n,x,y,k);
		}else{
			cout<<query(1,1,n,x,y)<<"\n";
		}
	}
	return 0;
}

自行查找。由于线段树的题大多数是多个标记的,所以题目不多。

标签:Lazy,now,int,tr,lazy,mid,tl,Tag,例题
From: https://www.cnblogs.com/wuyiming1263/p/18365531

相关文章

  • Oracle dataguard 搭建 oracle 11g ADG
    文章目录一、系统环境检查二、参数调整三、搭建ADG1、主库操作1、主库开启归档模式,此步骤需要重启数据库--5主库打开forcelogging--6主库修改DG相关参数--7修改之后验证:--8、主库添加standbyredologfile(根据MAA最佳实践,我们建议只为备用重做日志组,每......
  • 「对比评测」标准WPF DataGrid与DevExpress WPF GridControl有何不同?(一)
    DevExpressWPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpressWPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。无论是Office办公软件的衍伸产品,还是以数据为中心......
  • Python 正则表达式详解 带例题演示
    Python正则表达式正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。Python自1.5版本起增加了re模块,它提供Perl风格的正则表达式模式。re模块使Python语言拥有全部的正则表达式功能。compile函数根据一个模式字符串和可选的标志......
  • The 3rd Universal Cup. Stage 1: St. Petersburg Finalized Standings
    C.CherryPicking这道题用了一个类似ODT的题思路。首先我们可以想到是,如果删除某些选手,只有可能会导致区间的合并,不会导致区间的分裂。所以我们可以枚举一下$x$的值,然后找到需要删除的点。用set​维护相同且相邻区间,找到删除点所在的区间后,给区间长度减一。如果区间长度为空......
  • C语言典型例题46
    《C程序设计教程(第四版)——谭浩强》题目:习题3.6企业发放的奖金根据利润提成。利润I低于或等于100000元的,奖金可提成10%;                   利润高于100000元,低于200000元(100000<I≤200000)时,低于100000元的部分按10%提成,高于1000......
  • ansible Tags 标签
    目录Tags标签使用场景通过指定tags执行指定任务Tags标签Ansible的标签(Tags)功能可以给role,file,task,playbook,然后利用这些标签来指定要运行Playbook中的个别任务,或不执行指定的任务,并且它的语法非常简单使用场景选择性任务执行:当您不需要运行整个Playbook时,可以选择......
  • 易优CMS网站likearticle 功能:通过前3个TAG标签或前3个关键词,检索整站文档标题中含有t
    likearticle相关文档[基础用法]名称:likearticle功能:通过前3个TAG标签或前3个关键词,检索整站文档标题中含有tag标签或者关键词的相关文档,进行关联。在没有tag标签情况下,就以前3个关键词检索文档标题进行关联。这个标签随着数据量的增加可能会比较影响检索性能。    (温馨......
  • C语言典型例题45
    《C程序设计教程(第四版)——谭浩强》习题3.5给一个不多于5位的正整数,要求:     1.求出它是几位数;     2.分别输出每一位数字;     3.按逆序输出各位数字,例如:原数为321,输出为123代码://《C程序设计教程(第四版)——谭浩强》//习题3.5给一......
  • 意得辑真不错,85喆优惠码延长到25.12.31了我用editage意得辑润色SCI已经第4年了,今天他
    意得辑真不错,85喆优惠码延长到25.12.31了我用editage意得辑润色SCI已经第4年了,今天他家的学术支持老师让我写几句感受,那我真的感受太多了。因为下单太多一度被导师怀疑是在他家套经费。22年刚读博同时润色了三篇,被导师叫到办公室,问我是什么途径联系到的。我说师兄给说的,网上下......
  • 易优tag TAG调用标签-EyouCms手册
    【基础用法】名称:tag功能:TAG调用语法:{eyou:tagsort='now'getall='0'row='100'}{$field.tag}{/eyou:tag}参数:aid=''文档ID,在内容页可以不设置该属性typeid=''栏目ID,调取某个栏目下的全部TAGrow='100'返回广告列表总数getall=''获取类......