线段树入门

单点修改的线段树

线段树的建树

建树:代表对你的线段树进行初始化,那么这个结构体的每一个值都要在build函数里面进行初始化赋值。
p代表二叉树访问到哪一个节点
l和r代表线段树当前而二叉树结点的左右端点

void build(int p,int l,int r){//p代表二叉树访问到哪一个节点
	if(l==r){//访问到叶子节点
		tr[p]={l,l,w[l]};
	}else {
		tr[p].l=l,tr[p].r=r;//一定要注意给每一个二叉树分支进行初始化
		int mid=l+r>>1;//二叉树进行分支
		build(p<<1,l,mid);//访问左子节点
		build(p<<1|1,mid+1,r);//访问右子节点
		pushup(p);//对线段树其他的信息进行运算
	}
}

线段树的更新

x代表我要在哪一个点进行,增加v的操作

void update(int p,int x,int v){
	if(tr[p].l==tr[p].r&&tr[p].r==x){
		tr[p].sum+=v;	//叶子节点
	}else{//没有找到我们现在想要找到的点
		int mid=tr[p].l+tr[p].r>>1;
		if(x<=mid)
		update(p<<1,x,v);//如果在左边,那么就向左子节点查找
		else
		update(p<<1|1,x,v);//向右子节点查找
		pushup(p);//时刻更新线段树的值
	}
}

线段树的区间查询操作

区间覆盖的方法进行查询

个人认为覆盖的方法来查询线段树比较好理解

ll query(int p,int l,int r){
	//如果当前节点完全是查询区间的真子集那么就返回当前值
	if(tr[p].l>=l&&tr[p].r<=r){
		return tr[p].sum;	
	}
	//如果不是那就左右查询
	else {
		ll t=0;
		int mid=tr[p].l+tr[p].r>>1;
		if(l<=mid)t+=query(p<<1,l,r);
		if(r>mid)t+=query(p<<1|1,l,r);
		return t;
	}
}

区间相等的方法进行查询

ll query(int p,int l,int r){
	if(tr[p].l==l&&tr[p].r==r){
		return tr[p].sum;	//刚好查到我们现在的区间
	}
	else {
		int mid=tr[p].l+tr[p].r>>1;
		if(r<=mid)return query(p<<1,l,r);//如果和右子节点没关系,那就不用遍历
		else if(l>mid)return query(p<<1|1,l,r);
		else return query(p<<1|1,mid+1,r)+query(p<<1,l,mid);//如果两边都有关系,那就还是需要遍历一下
	}
}

例题1:敌兵布阵

题目传送门

#include<iostream>
#include<string>
using namespace std;
typedef long long ll;
ll w[2102100];
string qus="Query";
string miu="Sub";
string plu="Add";
string endd="End";
struct Segmentree{
	int l,r;
	ll sum;
}tr[210210];
void pushup(Segmentree &fa,Segmentree &son1,Segmentree &son2){
	fa.sum=son1.sum+son2.sum;
}
void pushup(int p){
	pushup(tr[p],tr[p<<1],tr[p<<1|1]);
}
void build(int p,int l,int r){
	if(l==r){
		tr[p]={l,l,w[l]};	
	//	cout<<tr[p].sum<<" ";
	}
	else {
		tr[p].l=l,tr[p].r=r;
		int mid=l+r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		pushup(p);
	}
}
void update(int p,int x,int v){
	if(tr[p].l==tr[p].r&&tr[p].r==x){
		tr[p].sum+=v;	
	}else{
		int mid=tr[p].l+tr[p].r>>1;
		if(x<=mid)
		update(p<<1,x,v);
		else
		update(p<<1|1,x,v);
		pushup(p);
	}
}
ll query(int p,int l,int r){
	if(tr[p].l>=l&&tr[p].r<=r){
	//	cout<<tr[p].l<<" "<<tr[p].sum<<"\n";
		return tr[p].sum;	
	}
	else {
		ll t=0;
		int mid=tr[p].l+tr[p].r>>1;
		if(l<=mid)t+=query(p<<1,l,r);
		if(r>mid)t+=query(p<<1|1,l,r);
	//	cout<<tr[p].l<<" "<<tr[p].r<<" "<<tr[p].sum<<"\n"; 
		return t;
	}
}
int main(){
	int T;
	cin>>T;
	int test=0;
	while(T--){
		test++;
		int n;
		cin>>n;
		for(int i=1;i<=n;i++)cin>>w[i];
		build(1,1,n);
		string t;int l,r;
		cout<<"Case "<<test<<":"<<endl; 
		while(cin>>t&&t!=endd){
			scanf("%d %d",&l,&r);
			//cin>>l>>r;
			if(t==plu){
				w[l]+=r;
				update(1,l,r);
			}else if(t==qus){
			//	cout<<l<<" "<<r<<"\n";
				printf("%lld\n",query(1,l,r));//-query(1,1,l)+w[l]
			}else if(t==miu){
				w[l]-=r;
				update(1,l,-r);
			}
		}
	}
}

例题2:区间最大公约数

题目传送门
其实根据欧几里得定理:
g c d ( x , y ) = g c d ( x , y − x ) gcd(x,y)=gcd(x,y-x) gcd(x,y)=gcd(x,yx)
拓展到多维那就是
g c d ( x , y , z ) = g c d ( x , y − x , z − y ) gcd(x,y,z)=gcd(x,y-x,z-y) gcd(x,y,z)=gcd(x,yx,zy)
比如我现在有n个数,后边的n-1个数都是差分序列啊,除了第一个数是它本身!这个性质很重要!因此我们需要开两个整数,一个存差分序列,还有一个当然是存sum,当然还有一个常识就是
g c d ( x , y , z ) = g c d ( g c d ( x , y ) , z ) gcd(x,y,z)=gcd(gcd(x,y),z) gcd(x,y,z)=gcd(gcd(x,y),z)

#include<iostream>
#include<string>
using namespace std;
typedef long long ll;
const ll N=505050;
ll w[N];
struct Segmentree{
	ll l,r;
	ll _gcd,sum;
}tr[4*N];
ll gcd(ll a,ll b){
	return b?gcd(b,a%b):a;
}
void pushup(Segmentree &fa,Segmentree &l,Segmentree &r){
	fa.sum=l.sum+r.sum;
	fa._gcd=gcd(l._gcd,r._gcd);
}
void pushup(ll p){
	tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum;
	tr[p]._gcd=gcd(tr[p<<1|1]._gcd,tr[p<<1]._gcd);
}
void build(ll p,ll l,ll r){
	if(l==r){
		tr[p].l=l;
		tr[p].r=l;
		tr[p].sum=w[l]-w[l-1];
		tr[p]._gcd=w[l]-w[l-1];
		return ;
	}
	tr[p].l=l;
	tr[p].r=r;
	ll mid=l+r>>1;
	if(r>=1+mid)build(p<<1|1,mid+1,r);
	if(l<=mid)build(p<<1,l,mid);
	pushup(p);
}
void update(ll p,ll x,ll u){
	if(tr[p].l==tr[p].r&&tr[p].l==x){
		tr[p].sum+=u;tr[p]._gcd=tr[p].sum;
		return;
	}
	ll mid=tr[p].l+tr[p].r>>1;
	if(x<=mid){
		update(p<<1,x,u);
	}
	if(x>mid)update(p<<1|1,x,u);
	pushup(p);
	return;
}
Segmentree query(ll p,ll l,ll r){
	if(tr[p].l>=l&&tr[p].r<=r)return tr[p];
	ll mid=tr[p].l+tr[p].r>>1;
	if(l>=mid+1){
		return query(p<<1|1,l,r);
	}
	else if(r<=mid)return query(p<<1,l,r);
	else {
		auto left =query(p<<1,l,r);
		auto right=query(p<<1|1,l,r);
		Segmentree t;
		pushup(t,left,right);
		return t;
	}
}
int main(){
	ll n,m;
	cin>>n>>m;
	for(ll i=1;i<=n;i++)cin>>w[i];
	build(1,1,n);
	while(m--){
		char c;
		getchar();
		cin>>c;
		if(c=='Q'){
			ll l,r;
			
			//auto right({0, 0, 0, 0});
			cin>>l>>r;auto t=query(1,1,l);
			printf("%lld\n",abs(gcd(query(1,l+1,r)._gcd,t.sum)));
			
		}else{
			ll l,r,d;
			cin>>l>>r>>d;
			update(1,l,d);
			if(r<n){
				update(1,r+1,-d);
			}
		}
	}
}

区间修改的线段树

上述我们说的线段树,有时候会涉及到区间修改的问题,但是虽然说有一些情况下,我们可以利用差分数组来把这个区间修改优化成单点修改,但是很多情况下我们无法用差分数组,如果还是利用单点修改的线段树,这样就会tle到飞起 ~ 这样的情况下我么就应该引入“懒标记”。

线段树的区间更新操作

当然是懒标记,懒标记实际上就是我们对遍历到的这个区间做一个标记,然后等以后要用的时候,用到哪个区间就把这个懒标记下传,懒标记所到之处,就更新这个线段树节点的相应的值。
可以说是非常巧妙的一个操作。

区间修改的线段树的声明

struct Segmentree{
	ll l,r;
	ll sum;
	ll lz;
}tr[1021000];

lz存放的是这个区间每一个值都应该加上lz这么多值。这个就是懒标记!

懒标记的更新和上传

懒标记的上传

切记上传完懒标记了以后一定要更新这个懒标记的值为0

void pushdown(Segmentree &fa,Segmentree &son1,Segmentree &son2){
	son1.lz+=fa.lz;son1.sum+=(son1.r-son1.l+1)*fa.lz;
	son2.lz+=fa.lz;son2.sum+=(son2.r-son2.l+1)*fa.lz;
	fa.lz=0;
}
void pushdown(ll p){
	if(tr[p].lz)pushdown(tr[p],tr[p<<1],tr[p<<1|1]);
}
懒标记的更新

一定要记住就是先更新懒标记(上传),然后再合并区间。

void update(ll p,ll l,ll r,ll v){
	if(tr[p].l>=l&&tr[p].r<=r){
	    tr[p].sum+=(ll)(tr[p].r-tr[p].l+1)*v;//更新节点值
		tr[p].lz+=v;//更新懒标记
	}else {
	    pushdown(p);
		ll mid=tr[p].l+tr[p].r>>1;
		if(l<=mid)
			update(p<<1,l,r,v);
		if(r>mid)update(p<<1|1,l,r,v);
		pushup(p);
	}
}

线段树的建树

void build(ll p,ll l,ll r){
	if(l==r){
		tr[p]={l,l,w[l],0};
	}else {
		tr[p].l=l,tr[p].r=r;
		ll mid=l+r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		pushup(p);
	}
}

区间修改的线段树的查询操作

ll query(ll p,ll l,ll r){
	if(tr[p].l>=l&&tr[p].r<=r){
		return tr[p].sum;
	}else {
		pushdown(p);
		ll mid=tr[p].l+tr[p].r>>1;
		ll k=0;
		if(l<=mid)
			k+=query(p<<1,l,r);
		if(r>mid)k+=query(p<<1|1,l,r);
		return k;
	}
}

例题1:A Simple Problem with Integers

题目传送门

#include<iostream>
#include<cstring>
#include<stdio.h>
using namespace std;
typedef long long ll;
ll w[2102100];
struct Segmentree{
	ll l,r;
	ll sum;
	ll lz;
}tr[1021000];
void pushup(Segmentree &fa,Segmentree &son1,Segmentree &son2){
	fa.sum=son1.sum+son2.sum;
}
void pushup(ll p){
	pushup(tr[p],tr[p<<1],tr[p<<1|1]);
}
void pushdown(Segmentree &fa,Segmentree &son1,Segmentree &son2){
	son1.lz+=fa.lz;son1.sum+=(son1.r-son1.l+1)*fa.lz;
	son2.lz+=fa.lz;son2.sum+=(son2.r-son2.l+1)*fa.lz;
	fa.lz=0;
}
void pushdown(ll p){
	if(tr[p].lz)pushdown(tr[p],tr[p<<1],tr[p<<1|1]);
}
void build(ll p,ll l,ll r){
	if(l==r){
		tr[p]={l,l,w[l],0};
	}else {
		tr[p].l=l,tr[p].r=r;
		ll mid=l+r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		pushup(p);
	}
}
void update(ll p,ll l,ll r,ll v){
	if(tr[p].l>=l&&tr[p].r<=r){
	    tr[p].sum+=(ll)(tr[p].r-tr[p].l+1)*v;//更新节点值
		tr[p].lz+=v;//更新懒标记
	}else {
	    pushdown(p);
		ll mid=tr[p].l+tr[p].r>>1;
		if(l<=mid)
			update(p<<1,l,r,v);
		if(r>mid)update(p<<1|1,l,r,v);
		pushup(p);
	}
}
ll query(ll p,ll l,ll r){
	if(tr[p].l>=l&&tr[p].r<=r){
		return tr[p].sum;
	}else {
		pushdown(p);
		ll mid=tr[p].l+tr[p].r>>1;
		ll k=0;
		if(l<=mid)
			k+=query(p<<1,l,r);
		if(r>mid)k+=query(p<<1|1,l,r);
		return k;
	}
}
int main(){
	ll n,m;
	cin>>n>>m;
	for(ll i=1;i<=n;i++)cin>>w[i];
	build(1,1,n);
	while(m--){
		char a;
		getchar();
		cin>>a;
		ll l,r;
		cin>>l>>r;
		if(a=='Q'){
			printf("%lld\n",query(1,l,r));
		}else {
			ll p;
			cin>>p;
			update(1,l,r,p);
    	}
	}
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值