线段树 Segment Tree

使用场景

  1. 对数列进行区间询问(包括最值、求和、乘积等询问)。

  2. 对数列进行区间修改(统一赋值、增减)。

使用思想

分治。

详解

手造一段数列:

2 5 9 1 7 6 5 3

给定 m 次操作:

type1. 将区间 [L,R]加上 val。

type2. 询问区间[L,R] 的元素和。

算法实现

  1. 建一棵线段树

  2. 查询函数

  3. 修改函数

时间复杂度分析

  1. 线段树结点编号达到 4×n。

  2. build O(n)

  3. query O(log n​)

  4. update O(n)(未优化)

未优化代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int a[N],tree[4*N], n, m;
void pushup(int cur)
{
	tree[cur]=tree[2*cur]+tree[2*cur+1];
	return ;
}
void build(int cur, int lt, int rt)
{
	if(lt==rt)
	{
		tree[cur]=a[lt];
		return ;
	}
	int mid=(lt+rt)>>1;
	build(cur*2,lt,mid);
	build(cur*2+1,mid+1,rt);
	pushup(cur);
	return ;
}
int query(int cur, int lt, int rt, int qx, int qy)
{
	if(qy<lt||qx>rt)
	{
		return 0;
	}
	if(qx<=lt&&rt<=qy)
	{
		return tree[cur];
	}
	int mid=lt+rt>>1;
	return query(cur*2,lt,mid,qx,qy)+query(cur*2+1,mid+1,rt,qx,qy);
}
void update(int cur, int lt, int rt, int qx, int qy, int val)
{
	if(qy<lt||qx>rt)
	{
		return ;
	}
	if(lt==rt)
	{
		tree[cur]+=val;
		return ;
	}
	int mid=lt+rt>>1;
	update(cur*2,lt,mid,qx,qy,val);
	update(cur*2+1,mid+1,rt,qx,qy,val);
	pushup(cur);
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];	
	}
	build(1,1,n);
	while(m--)
	{
		int opt, x, y, val;
		cin>>opt>>x>>y;
		if(opt==1)
		{
			cin>>val;
			update(1,1,n,x,y,val);
		}
		if(opt==2)
		{
			cout<<query(1,1,n,x,y)<<"\n";
		}
	}
}

线段树的优化

很容易发现,单次修改的时间复杂度很慢。

考虑优化,采用懒标记。

性质:线段树的修改是为询问而服务的。

维护标记 tag_{cur} 表示结点 cur 需要修改的值。

修改后 update 函数单次时间复杂度可达 O(log n​)

优化后代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int a[N], n, m, tag[4*N], tree[4*N];
void pushup(int cur)
{
	tree[cur]=tree[cur*2]+tree[cur*2+1];
	return ;
}
void addtag(int cur, int lt, int rt, int val)
{
	tag[cur]+=val;
	tree[cur]+=(rt-lt+1)*val;
	return ;
}
void pushdown(int cur, int lt, int rt)
{
	if(tag[cur]==0)
	{
		return ;
	}
	int mid=lt+rt>>1;
	addtag(cur*2,lt,mid,tag[cur]);
	addtag(cur*2+1,mid+1,rt,tag[cur]);
	tag[cur]=0;
	return ;
} 
void build(int cur, int lt, int rt)
{
	if(lt==rt)
	{
		tree[cur]=a[lt];
		return ;
	}
	int mid=lt+rt>>1;
	build(cur*2,lt,mid);
	build(cur*2+1,mid+1,rt);
	pushup(cur);
	return ;
}
int query(int cur, int lt, int rt, int qx, int qy)
{
	if(qy<lt||qx>rt)
	{
		return 0;
	}
	if(qx<=lt&&rt<=qy)
	{
		return tree[cur];
	}
	pushdown(cur,lt,rt);
	int mid=lt+rt>>1;
	return query(cur*2,lt,mid,qx,qy)+query(cur*2+1,mid+1,rt,qx,qy);
}
void update(int cur, int lt, int rt, int qx, int qy, int val)
{
	if(qy<lt||qx>rt)
	{
		return ;
	}
	if(qx<=lt&&rt<=qy)
	{
		addtag(cur,lt,rt,val);
		return ;
	}
	pushdown(cur,lt,rt);
	int mid=lt+rt>>1;
	update(cur*2,lt,mid,qx,qy,val);
	update(cur*2+1,mid+1,rt,qx,qy,val);
	pushup(cur);
	return ;
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	build(1,1,n);
	while(m--)
	{
		int opt, x, y, val;
		cin>>opt>>x>>y;
		if(opt==1)
		{
			cin>>val;
			update(1,1,n,x,y,val);
		}
		else
		{
			cout<<query(1,1,n,x,y)<<"\n";
		}
	}
	return 0;
}

带乘法的线段树

多维护一个乘法标记,随加法标记更新,注意运算顺序。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int a[N], n, m, tag[4*N], tree[4*N], mod, mul[4*N];
void pushup(int cur)
{
	tree[cur]=tree[cur*2]%mod+tree[cur*2+1]%mod;
	return ;
}
void addtag(int cur, int lt, int rt, int val)
{
	tag[cur]+=val;
	tree[cur]+=(rt-lt+1)*val%mod;
	return ; 	
}
void addtag1(int cur, int lt, int rt, int val)
{
	tag[cur]=tag[cur]*val%mod;
	mul[cur]=mul[cur]*val%mod;
	tree[cur]=tree[cur]*val%mod;
	return ;
}
void pushdown(int cur, int lt, int rt)
{
	if(tag[cur]==0&&mul[cur]==1)
	{
		return ;
	}
	int mid=(lt+rt)>>1;
	addtag1(cur*2,lt,mid,mul[cur]);
	addtag1(cur*2+1,mid+1,rt,mul[cur]);
	addtag(cur*2,lt,mid,tag[cur]);
	addtag(cur*2+1,mid+1,rt,tag[cur]);
	tag[cur]=0;
	mul[cur]=1;
	return ;
} 
void build(int cur, int lt, int rt)
{
	if(lt==rt)
	{
		tree[cur]=a[lt];
		return ;
	}
	int mid=lt+rt>>1;
	build(cur*2,lt,mid);
	build(cur*2+1,mid+1,rt);
	pushup(cur);
	return ;
}
int query(int cur, int lt, int rt, int qx, int qy)
{
	if(qy<lt||qx>rt)
	{
		return 0;
	}
	if(qx<=lt&&rt<=qy)
	{
		return tree[cur];
	}
	pushdown(cur,lt,rt);
	int mid=lt+rt>>1;
	return query(cur*2,lt,mid,qx,qy)+query(cur*2+1,mid+1,rt,qx,qy);
}
void update(int cur, int lt, int rt, int qx, int qy, int val)
{
	if(qy<lt||qx>rt)
	{
		return ;
	}
	if(qx<=lt&&rt<=qy)
	{
		addtag(cur,lt,rt,val);
		return ;
	}
	pushdown(cur,lt,rt);
	int mid=lt+rt>>1;
	update(cur*2,lt,mid,qx,qy,val);
	update(cur*2+1,mid+1,rt,qx,qy,val);
	pushup(cur);
	return ;
}
void update1(int cur, int lt, int rt, int qx, int qy, int val)
{
	if(qy<lt||qx>rt)
	{
		return ;
	}
	if(qx<=lt&&rt<=qy)
	{
		addtag1(cur,lt,rt,val);
		return ;
	}
	pushdown(cur,lt,rt);
	int mid=lt+rt>>1;
	update1(cur*2,lt,mid,qx,qy,val);
	update1(cur*2+1,mid+1,rt,qx,qy,val);
	pushup(cur);
	return ;
}
signed main()
{
	cin>>n>>m>>mod;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	for(int i=1;i<=4*n;i++)
	{
		mul[i]=1;
	}
	build(1,1,n);
	while(m--)
	{
		int opt, x, y, val;
		cin>>opt>>x>>y;
		if(opt==2)
		{
			cin>>val;
			update(1,1,n,x,y,val);
		}
		else if(opt==1)
		{
			cin>>val;
			update1(1,1,n,x,y,val);
		}
		else
		{
			cout<<query(1,1,n,x,y)%mod<<"\n";
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值