2021-07-10 线段树专题

线段树专题

写在前面:

​ 哇咔咔咔咔。。没有想到我更了吧!!

​ 其实是因为学校每天都制作比赛让我们做,正好打个总结,保留一下此时我的想法。

​ 话不多说,开始!!

线段树:

首当其冲的,便是我们的线段树,线段树是一颗,而且还是一颗完全二叉树,它是由左右子节点的区间合并而成,形容一段区间的树。一般来讲,线段树一般用于区间查询问题,也就是对其中某一点或某一段区间数据进行变更后,查询区间内最大值。线段树这个名称的来源是指每一个节点都代表了一段区间,因此取名线段树

空口无凭,我们找到几道例题:

最大值:

在N(1<=N<=100000)个数A1…An组成的序列上进行M(1<=M<=100000)次操作,操作有两种:

(1)1 x y:表示修改A[x]为y;

(1)2 x y:询问x到y之间的最大值。

对于每个操作(2)输出对应的答案。

题解:

ok,这种题,无非就是裸题,这正好适合初学者使用,让我们来做一下吧。

一:修改

修改的代码很简单,我们从上到下一直查找,修改后回溯,只变更路径的父亲节点就可以了,具体的话,因为是树,那么,我们就可以使用DFS作序来进行修改即可。来看看吧。

code:

void backspace(int i,int x,int y) 
{
	if(y<lt||x>rt) return;
	if(x==y) 
	{
		f[i]=root;
		return;
	}
	int m=(x+y)/2;
	backspace(i*2,x,m); 
	backspace(i*2+1,m+1,y);
	f[i]=max(f[i*2],f[i*2+1]);
}

二:查询

线段树的及其核心之一——**O(1)**查询~~(可惜我不会做)~~,我来做一个简单一点的O(log(n))查询吧,也是依靠线段树查询。

code:

void question(int i, int x, int y) 
{
	if(y<lt||x>rt) return;
	if(x>=lt&&y<=rt) 
	{
		root=max(root,f[i]);
		return;
	}
	int m=(x+y)/2;
	question(i*2,x,m);
	question(i*2+1,m+1,y);
}

三:主代码

主代码就非常简单了,只需合并就可以Accept,来!

code:

#include<bits/stdc++.h> 
using namespace std;
int n,m,h,x,y;
int a[100001];
int f[400001];
int lt,rt,root;
void maketree(int i,int x,int y)
{
	if(x==y) 
	{
		f[i]=a[x];
		return;
	}
	int m=(x + y)/2;
	maketree(i*2, x, m);
	maketree(i*2+1, m+1,y);
	f[i]=max(f[i*2], f[i*2+1]);
}
void backspace(int i,int x,int y) 
{
	if(y<lt||x>rt) return;
	if(x==y) 
	{
		f[i]=root;
		return;
	}
	int m=(x+y)/2;
	backspace(i*2,x,m); 
	backspace(i*2+1,m+1,y);
	f[i]=max(f[i*2],f[i*2+1]);
}
void question(int i, int x, int y) 
{
	if(y<lt||x>rt) return;
	if(x>=lt&&y<=rt) 
	{
		root=max(root,f[i]);
		return;
	}
	int m=(x+y)/2;
	question(i*2,x,m);
	question(i*2+1,m+1,y);
}

int main() 
{
	scanf("%d", &n);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]);
	maketree(1,1,n);
	scanf("%d",&m);
	for(int i=1; i<=m; i++) 
	{
		scanf("%d%d%d",&h,&x,&y);
		if(h==1)
		{
			lt=rt=x,root=y;
			backspace(1,1,n);
		} 
		else 
		{
			lt=x,rt=y; root=-300000000; //这里的root并不指根,请注意
			question(1,1,n);
			printf("%d\n",root);
		}
	}
	return 0; 
}
最大值(Lazy):

在N(1<=N<=100000)个数A1…An组成的序列上进行M(1<=M<=100000)次操作,操作有两种:

(1)1 L R C:表示把A[L]到A[R]增加C(C的绝对值不超过10000);

(2)2 L R:询问A[L]到A[R]之间的最大值。

对于每个操作(2)输出对应的答案。

题解:

ok,这种题,无非也是裸题,这正好适合初学者使用,让我们来做一下吧。

一:修改

首先,我们要讲线段树另外一个核心:Lazy数组,他的主要功能是用来修改一个区间的值,用来减少时间复杂度。这种高深莫测的理论相信初学者也不是很理解,总的来说,是这样的。

story:

有一个项目完成了,老板很高兴,当场发了10万奖金给这个部门。部门主管理应上来说是因该直接发给手下两个人(他自己的薪资很高,不需要这十万块),但是,这个主管很忙,没时间发工资,就对底下的两个人说你们先记着,我也记着,到时候到发工资时一起给,于是,两个新人记着主管给我5万,主管记着要给新人们一共十万。等到领工资那天,两个新人分别拿着5万走了,将主管给我5万忘了,而主管发完工资后,也将给新人们一共十万忘了…

从上面的小故事里,你就可以知道lazy数组其实就是用来记账的。

那如何实现呢?让我们看看代码吧

code:

void change(ll l,ll r,ll s,ll aa,ll bb,ll cc)
{
	if(fy[s]!=0) 
	{
		t[s]+=fy[s]; 
		fy[s*2]+=fy[s];
		fy[s*2+1]+=fy[s]; 
		fy[s]=0; 
	}
	if(l==aa&&r==bb)
	{
		fy[s]+=cc; 
		return;
	}
	ll mid=(l+r)/2;
	if(aa<=mid) change(l,mid,s*2,aa,min(bb,mid),cc);
	if(bb>mid) change(mid+1,r,s*2+1,max(aa,mid+1),bb,cc); 
	t[s]=max(t[s*2]+fy[s*2],t[s*2+1]+fy[s*2+1]); 
} 
//fy[] = lazy[]

二:查询

跟上文的查询一样,来看看吧

code:

void find(ll l,ll r,ll s,ll aa,ll bb)
{
	if(fy[s]!=0)
	{
		t[s]+=fy[s];
		fy[s*2]+=fy[s];
		fy[s*2+1]+=fy[s];
		fy[s]=0;
	} 
	if(l==aa&&r==bb)
	{
		ans=max(ans,t[s]);
		return;
	} 
	ll mid=(l+r)/2;
	if(aa<=mid) find(l,mid,s*2,aa,min(bb,mid));
	if(bb>mid) find(mid+1,r,s*2+1,max(aa,mid+1),bb); 
} 
//这里的程序与上文不同是因为为了lazy数组拜年话

三:主代码

主代码就非常简单了,只需合并就可以Accept,来!

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
ll n,m,z,x,y,c,ans,t[4000005],a[1000005],fy[4000005]; 
void make(ll l,ll r,ll s)
{
	if(l==r)
	{
		t[s]=a[l];
		return;
	}
	ll mid=(l+r)/2;
	make(l,mid,s*2);
	make(mid+1,r,s*2+1);
	t[s]=max(t[s*2],t[s*2+1]);
} 
void change(ll l,ll r,ll s,ll aa,ll bb,ll cc)
{
	if(fy[s]!=0) 
	{
		t[s]+=fy[s]; 
		fy[s*2]+=fy[s];
		fy[s*2+1]+=fy[s]; 
		fy[s]=0; 
	}
	if(l==aa&&r==bb)
	{
		fy[s]+=cc; 
		return;
	}
	ll mid=(l+r)/2;
	if(aa<=mid) change(l,mid,s*2,aa,min(bb,mid),cc);
	if(bb>mid) change(mid+1,r,s*2+1,max(aa,mid+1),bb,cc); 
	t[s]=max(t[s*2]+fy[s*2],t[s*2+1]+fy[s*2+1]); 
} 
void find(ll l,ll r,ll s,ll aa,ll bb)
{
	if(fy[s]!=0)
	{
		t[s]+=fy[s];
		fy[s*2]+=fy[s];
		fy[s*2+1]+=fy[s];
		fy[s]=0;
	} 
	if(l==aa&&r==bb)
	{
		ans=max(ans,t[s]);
		return;
	} 
	ll mid=(l+r)/2;
	if(aa<=mid) find(l,mid,s*2,aa,min(bb,mid));
	if(bb>mid) find(mid+1,r,s*2+1,max(aa,mid+1),bb); 
} 
int main()
{
	scanf("%lld",&n);
	for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
	make(1,n,1);
	scanf("%lld",&m);
	for(ll i=1;i<=m;i++)
	{
		scanf("%lld",&z);
		if(z==1)
		{
			scanf("%lld%lld%lld",&x,&y,&c);
			change(1,n,1,x,y,c);
		}
		else
		{
			scanf("%lld%lld",&x,&y);
			ans=-3*1e9;
			find(1,n,1,x,y);
			printf("%lld\n",ans);
		}
	}
	return 0;
}

写在最后:

这两道例题,都是最基本的线段树使用,但却包含了线段树的所有基本定理,因此,通过这两道题,我们其实就能管中窥豹,来做出线段树,掌握线段树。线段树是一种非常重要的算法,大家要认真学习!!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值