DAY_4(线段树)

呼~好像好难的样子。。

线段树,又称区间树,给1到n个点,每个点存入了一些信息,用[ L,R 表示下标从L到R的区间信息

原理:将 [1,n] 分解成若干特定子区间(数量<=4*n),后将每一个区间 [L,R]  分解成少量特定子区间,通过对这些少量子区间的修改或统计来实现对[L,R] 的修改或统计

线段树是一棵二叉树,是平衡二叉树,但不是完全二叉树

 属性:

1、每个区间的长度是区间诶正数的个数

2、叶子节点程度为1,不可再分

3、[a,b] 对应子区间:[ a,(a+b)/2 ],[ (a+b)/2+1,b ]

4、线段树高度=[log2(b-a+1)]+1

5、线段树把区间上的热议一条现代都分成不超过2logN条——任一区间分成节点,每一层超过2个

线段树定义

f1:

#define maxn 100007
int SegTree[maxn>>2];//线段树 
//int Lazy[maxn>>2];//延迟更新标记 
int A[maxn];//原始数组 

f2:(常用)——方便添加元素

int a[N];//原始数组 
struct tr
{
	ll left,right;
	ll num;//节点值 
	ll lazy;//延迟更新标记 
}tree[N<<2];//线段树 

线段树构造

void build(int rt,int l,int r)
{//构造根为rt,区间为[l,r]的线段树 
	tree[rt].left=l;
	tree[rt].right=r;
	tree[rt].lazy=0;
	if(tree[rt].left==tree[rt].right)
	{//确认过眼神,她是叶子 
		tree[rt].num=a[tree[rt].right];
		return ;
	}
	int mid=(r+l)>>1;
	build(rt<<1,l,mid);//递归构造左子树 
	build(rt<<1|1,mid+1,r);//递归构造右子树 
	tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;
	//回溯,向上更新 
	return ;
}

单点更新

void my_plus(int rt,int dis,int k)
{//单点更新
	tree[rt].num+=k;//更新
	if(tree[rt].left==tree[rt].right)
	return ;
	if(dis<=tree[rt<<1].right)
	my_plus(rt<<1,dis,k);//递归更新左子树
	if(dis>=tree[rt<<1|1].left)
	my_plus(rt<<1|1,dis,k);//递归更新右子树
}

区间查询

void search(int rt,int l,int r)
{//区间查询 
	if(tree[rt].left>=l&&tree[rt].right<=r)
	{
		ans+=tree[rt].num;
		return ;
	}
	if(tree[rt<<1].right>=l)
	search(rt<<1,l,r);
	if(tree[rt<<1|1].left<=r)
	search(rt<<1|1,l,r);
    //遍历左右子树与[L,R]重叠部分
}

区间更新

更新所有叶子节点,若一次性更新完,需要时间复杂度O(n),故引入“延迟标记

延迟标记:每个节点上新增一个标记,记录这个节点是否进行了某种修改(这种操作会影响子节点),对于任意区间的修改,先按区间查询方式划分成线段树中的节点,然后修改这些节点的信息,并给这些节点标记上代表这种修改的标记

        在修改和查询时,若到一个节点P,并决定是否考虑其他节点,那么需要看P是否被标记,若有则要按照标记修改其子节点的信息,并给子节点标上相同标记,同时删除P点标记

延迟更新

懒惰理由:1、多次更新,一次下推;2、无需要,不下推

懒惰原则:可懒惰,但不可出错

线段树定义:struct中加入  int lazy;

线段树构造:build函数中(初始化延时标记为0,SegTree[rt].lazy=0)

区间更新

void addplus(int rt,int l,int r,int k)
{
	if(l<=tree[rt].left&&r>=tree[rt].right)
	{
		tree[rt].lazy+=k;//懒惰值更新
		tree[rt].num+=(tree[rt].right-tree[rt].left+1)*k;
        //更新数字和,向上保证正确 
		return ;
	}
	if(tree[rt].lazy)
	pushdown(rt);//下推以后,才准确更新子节点 
	if(l<=tree[rt<<1].right)
	addplus(rt<<1,l,r,k);
	if(r>=tree[rt<<1|1].left)
	addplus(rt<<1|1,l,r,k);
	tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;//回溯,更新本节点信息 
}
PushDown函数
void pushdown(ll rt)
{
	tree[rt<<1].lazy+=tree[rt].lazy;
	tree[rt<<1|1].lazy+=tree[rt].lazy;
	tree[rt<<1].num+=tree[rt].lazy*(tree[rt<<1].right-tree[rt<<1].left+1);
	tree[rt<<1|1].num+=tree[rt].lazy*(tree[rt<<1|1].right-tree[rt<<1|1].left+1);
	tree[rt].lazy=0;//一定要记得清除标记!!!
	return ;
}

区间查询

void search(ll rt,ll l,ll r)
{
	if(tree[rt].left>=l&&tree[rt].right<=r)
	{
		ans+=tree[rt].num;
		return ;
	}
	if(tree[rt].lazy)
	pushdown(rt);//懒惰标记下推,更新子节点
	if(l<=tree[rt<<1].right)
	search(rt<<1,l,r);//左子树与[L,R]重叠部分
	if(r>=tree[rt<<1|1].left)
	search(rt<<1|1,l,r);//右子树与[L,R]重叠部分
}

 因为第四天有事,所以到了第五天才写了点题目,算是巩固一下知识吧

P3374 【模板】树状数组 1(单点更新+区间查询)

之前在树状数组文章里用了树状数组写过的,现在发现这可以用线段树来写的,同样都是单点/区间更新还有单点/区间查询,代码:

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5; 
int n,m;
int ans;
int he;//线段树高度 
int input[N];//原始数组 
struct node
{
	int left,right;
	int num;
}tree[N<<2];
void build(int left,int right,int rt)
{//构造线段树 
	he++;//高度++ 
	tree[rt].left=left;
	tree[rt].right=right;
	if(left==right)
	return ;
	int mid=(left+right)>>1;
	build(left,mid,rt<<1);//构造左子树 
	build(mid+1,right,rt<<1|1);//构造右子树 
}
int add(int rt)
{
	if(tree[rt].left==tree[rt].right)
	{
		tree[rt].num=input[tree[rt].right];
		return tree[rt].num;
	}
	tree[rt].num=add(rt<<1)+add(rt<<1|1);
	return tree[rt].num;
}
void my_plus(int rt,int dis,int k)
{//单点更新
	tree[rt].num+=k;
	if(tree[rt].left==tree[rt].right)
	return ;
	if(dis<=tree[rt<<1].right)
	my_plus(rt<<1,dis,k);
	if(dis>=tree[rt<<1|1].left)
	my_plus(rt<<1|1,dis,k);
}
void search(int rt,int l,int r)
{//区间查询 
	if(tree[rt].left>=l&&tree[rt].right<=r)
	{
		ans+=tree[rt].num;
		return ;
	}
	if(tree[rt<<1].right>=l)
	search(rt<<1,l,r);
	if(tree[rt<<1|1].left<=r)
	search(rt<<1|1,l,r);
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>input[i];
	build(1,n,1);
	add(1);
	for(int i=1;i<=m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		if(a==1)
		my_plus(1,b,c);
		if(a==2)
		{
			ans=0;
			search(1,b,c);
			cout<<ans<<endl; 
		}
	}
}

P3368 【模板】树状数组 2(区间更新+单点查询)

在掌握了模版后,这题当然也是不难的啦

直接上代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m;
int a[N];
int x,y,k;
int com;
int ans;
struct tre
{
	int num;
	int left;
	int right;
	int lazy;
}tree[N<<2];
void build(int rt,int l,int r)
{//建树 
	tree[rt].lazy=0;//懒惰标记初始化为0 
	tree[rt].left=l;
	tree[rt].right=r;
	if(l==r)
	{
		tree[rt].num=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;
	return ;
}
void pushdown(int rt)
{//标记下传 
	tree[rt<<1].lazy+=tree[rt].lazy;
	tree[rt<<1|1].lazy+=tree[rt].lazy;
	tree[rt<<1].num+=tree[rt].lazy*(tree[rt<<1].right-tree[rt<<1].left+1);
	tree[rt<<1|1].num+=tree[rt].lazy*(tree[rt<<1|1].right-tree[rt<<1|1].left+1);
	tree[rt].lazy=0;
	return ;
}
void addplus(int rt,int l,int r,int k)
{//区间更新 
	if(tree[rt].left>=l&&tree[rt].right<=r)
	{
		tree[rt].lazy+=k;
		tree[rt].num+=(tree[rt].right-tree[rt].left+1)*k;
		return ;
	}
	if(tree[rt].lazy)
	pushdown(rt);
	if(l<=tree[rt<<1].right)
	addplus(rt<<1,l,r,k);
	if(r>=tree[rt<<1|1].left)
	addplus(rt<<1|1,l,r,k);
	tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;//再次更新 
}
void search(int rt,int k)
{//单点查询 
	if(tree[rt].left==tree[rt].right)
	{
		ans+=tree[rt].num;
		return ;
	}
	if(tree[rt].lazy)
	pushdown(rt);
	if(k<=tree[rt<<1].right)
	search(rt<<1,k);
	if(k>=tree[rt<<1|1].left)
	search(rt<<1|1,k);
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		cin>>com;
		if(com==1)
		{
			cin>>x>>y>>k;
			addplus(1,x,y,k);//区间修改 
		}
		if(com==2)
		{
			ans=0;
			cin>>x;
			search(1,x);
			cout<<ans<<endl;
		}
	}
	return 0;
}

 P3372 【模板】线段树 1(区间更新+区间查询)

也就是把前面两个代码结合一下,不过一定要注意,查询的时候需要加上pushdown函数!!!否则,下面的节点得不到实时更新,还要设long long,不然会卡三个点。。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
#define ll long long
ll n,m;
ll com;
ll ans;
ll x,y,z;
ll a[N];//原始数组 
struct tr
{
	ll left,right;
	ll num;//节点值 
	ll lazy;//延迟更新标记 
}tree[N<<2];//线段树 
void build(ll rt,ll l,ll r)
{//构造根为rt,区间为[l,r]的线段树 
	tree[rt].left=l;
	tree[rt].right=r;
	tree[rt].lazy=0;
	if(tree[rt].left==tree[rt].right)
	{//确认过眼神,她是叶子 
		tree[rt].num=a[tree[rt].right];
		return ;
	}
	ll mid=(r+l)>>1;
	build(rt<<1,l,mid);//递归构造左子树 
	build(rt<<1|1,mid+1,r);//递归构造右子树 
	tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;
	//回溯,向上更新 
	return ;
}
void pushdown(ll rt)
{
	tree[rt<<1].lazy+=tree[rt].lazy;
	tree[rt<<1|1].lazy+=tree[rt].lazy;
	tree[rt<<1].num+=tree[rt].lazy*(tree[rt<<1].right-tree[rt<<1].left+1);
	tree[rt<<1|1].num+=tree[rt].lazy*(tree[rt<<1|1].right-tree[rt<<1|1].left+1);
	tree[rt].lazy=0;
	return ;
}
void addplus(ll rt,ll l,ll r,ll k)
{
	if(l<=tree[rt].left&&r>=tree[rt].right)
	{
		tree[rt].lazy+=k;
		tree[rt].num+=(tree[rt].right-tree[rt].left+1)*k;
		return ;
	}
	if(tree[rt].lazy)
	pushdown(rt);
	if(l<=tree[rt<<1].right)
	addplus(rt<<1,l,r,k);
	if(r>=tree[rt<<1|1].left)
	addplus(rt<<1|1,l,r,k);
	tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;
}
void search(ll rt,ll l,ll r)
{
	if(tree[rt].left>=l&&tree[rt].right<=r)
	{
		ans+=tree[rt].num;
		return ;
	}
	if(tree[rt].lazy)
	pushdown(rt);
	if(l<=tree[rt<<1].right)
	search(rt<<1,l,r);
	if(r>=tree[rt<<1|1].left)
	search(rt<<1|1,l,r);
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		cin>>com;
		if(com==1)
		{
			cin>>x>>y>>z;
			addplus(1,x,y,z);
		}
		if(com==2)
		{
			cin>>x>>y;
			ans=0;
			search(1,x,y);
			cout<<ans<<endl;
		}
	}
}

 P3373 【模板】线段树 2(区间更新乘法+区间查询)

这题好啊!!首先是区间更新,立即就能想到懒惰标记 ~~lazy~~,然后再看题面。用上了乘法,还有加法,那么这俩同时出现的时候,我们就不得不设俩懒惰标了。再看乘法和加法混杂,那么就一定会有顺序问题,那么这里一定得是先乘后加!!!!

先把需要加的乘上倍数,那么这个需要加上的数,就被更新成新子树需要加上的数了,后来的操作只要将原来的根节点的值乘以倍数再加上lazyadd即可

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
#define ll long long
ll n,m,q;
ll a[N];
ll com,x,y,z;
ll ans;
struct tr
{
	ll left;
	ll right;
	ll lazymul;
	ll lazyadd;
	ll num;
}tree[N<<2];
void build(ll l,ll r,ll rt)
{
	tree[rt].left=l;
	tree[rt].right=r;
	tree[rt].lazyadd=0;
	tree[rt].lazymul=1;
	if(l==r)
	{
		tree[rt].num=a[l]%m;
		return ;
	}
	ll mid=(l+r)>>1;
	build(l,mid,rt<<1);
	build(mid+1,r,rt<<1|1);
	tree[rt].num=(tree[rt<<1].num+tree[rt<<1|1].num)%m;
	return ;
}
void pushdown(ll rt)
{
	tree[rt<<1].lazymul=(tree[rt<<1].lazymul*tree[rt].lazymul)%m;
	tree[rt<<1|1].lazymul=(tree[rt<<1|1].lazymul*tree[rt].lazymul)%m;
	tree[rt<<1].lazyadd=((tree[rt<<1].lazyadd*tree[rt].lazymul)%m+tree[rt].lazyadd)%m;
	tree[rt<<1|1].lazyadd=((tree[rt<<1|1].lazyadd*tree[rt].lazymul)%m+tree[rt].lazyadd)%m;
	tree[rt<<1].num=((tree[rt<<1].num*tree[rt].lazymul)%m+(tree[rt].lazyadd*(tree[rt<<1].right-tree[rt<<1].left+1))%m)%m;
	tree[rt<<1|1].num=((tree[rt<<1|1].num*tree[rt].lazymul)+(tree[rt].lazyadd*(tree[rt<<1|1].right-tree[rt<<1|1].left+1))%m)%m;
	tree[rt].lazyadd=0;
	tree[rt].lazymul=1;
}
void multiple(ll rt,ll l,ll r,ll k)
{
	if(tree[rt].left>=l&&tree[rt].right<=r)
	{
		tree[rt].num=(tree[rt].num*k)%m;
		tree[rt].lazymul=(tree[rt].lazymul*k)%m;
		tree[rt].lazyadd=(tree[rt].lazyadd*k)%m;
		return ;
	}
	pushdown(rt);
	if(l<=tree[rt<<1].right)
	multiple(rt<<1,l,r,k);
	if(r>=tree[rt<<1|1].left)
	multiple(rt<<1|1,l,r,k);
	tree[rt].num=(tree[rt<<1].num+tree[rt<<1|1].num)%m;
	return ;
}
void add(ll rt,ll l,ll r,ll k)
{
	if(tree[rt].left>=l&&tree[rt].right<=r)
	{
		tree[rt].num+=((tree[rt].right-tree[rt].left+1)*k)%m;
		tree[rt].lazyadd=(tree[rt].lazyadd+k)%m;
		return ;
	}
	pushdown(rt);
	if(l<=tree[rt<<1].right)
	add(rt<<1,l,r,k);
	if(r>=tree[rt<<1|1].left)
	add(rt<<1|1,l,r,k);
	tree[rt].num=(tree[rt<<1].num+tree[rt<<1|1].num)%m;
	return ;
}
void query(ll rt,ll l,ll r)
{
	if(tree[rt].left>=l&&tree[rt].right<=r)
	{
		ans=(ans+tree[rt].num)%m;
		return ;
	}
	pushdown(rt);
	if(l<=tree[rt<<1].right)
	query(rt<<1,l,r);
	if(r>=tree[rt<<1|1].left)
	query(rt<<1|1,l,r);
}
int main()
{
	cin>>n>>q>>m;
	for(ll i=1;i<=n;i++)
	cin>>a[i];
	build(1,n,1);//(l,r,rt)
	for(ll i=1;i<=q;i++)
	{
		cin>>com;
		if(com==1)
		{
			cin>>x>>y>>z;
			multiple(1,x,y,z);
		}
		if(com==2)
		{
			cin>>x>>y>>z;
			add(1,x,y,z);
		}
		if(com==3)
		{
			ans=0;
			cin>>x>>y;
			query(1,x,y);
			cout<<ans%m<<endl;
		}
	}
	return 0;
}

 不开long long见祖宗。。

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值