线段树学习笔记

1. 原理

线段树是啥呢?

M14rgU.jpg

如图,就是一个数组的线段树。所以,线段树的前提是要有一个原数组。

假如,我想让你求数组中的区间和。那么肯定就有巨佬出现了:

M3CBwR.png

呃……(空气安静3秒钟)

好吧,我们求区间最大值。这样,就能让这位巨佬哑口无言。

我们把数组分成多个段,就像上面的图,1-n 可以分为 1 - n/2 和 n/2+1 - n两个区域,然后又可以分为四个区域……所以呢,这是一个二叉树。只要我们最后分下去的区间已经分不下去了,只剩一个点了,那么就可以结束了。

M17dpt.png

如图,就是一个对于a数组的线段树。同一个颜色的是同一层。因为我们肯定没有2.5这种东西,所以我们需要取整,导致看起来不对称。

2. 建树

我们需要很多变量,我们慢慢来。

首先,我们先说明,每个节点的左儿子编号为2n,右儿子为2n+1。

以下,是我们需要的数组

int zhi[10001];//原数组 
int fak[10001];//线段树数组 
int qu_l[10001];//维护的区间 左范围 
int qu_r[10001];//维护的区间 右范围 

原数组的输入我就不讲了,如果你真的不知道,我建议你看这个:传送门

void in(int l,int r,int hao)//左范围,右范围,编号 
{
	if(l==r)
	{
		fak[hao]=zhi[l];
		return ;
	}
	
	in(l,(l+r)/2,hao*2);
	in((l+r)/2+1,r,hao*2+1);
	
	fak[hao]=max(fak[hao*2],fak[hao*2+1]); 
	return ;
}

我们通过

in(1,n,1);

就可以让它建树啦!但是呢,我们还需要让它存下对应节点所维护的区间。所以,我们需要加上一些代码。

void in(int l,int r,int hao)//左,右,编号 
{
	if(l==r)
	{
		qu_l[hao]=l;
		qu_r[hao]=r;//忘记记录害死猫 
		fak[hao]=zhi[l];
		return ;
	}
	
	in(l,(l+r)/2,hao*2);
	in((l+r)/2+1,r,hao*2+1);
	
	fak[hao]=max(fak[hao*2],fak[hao*2+1]); 
	
	qu_l[hao]=l;
	qu_r[hao]=r;//记录其维护的区间 
	return ;
}

3. 单点修改

如果我们要修改某些点的话,假如我们改第二个为5

M1qbNQ.png

只要包含这个点的,都需要修改。

void fix(int hao,int h,int zhi)//线段树所在编号  单点修改需要的编号   修改的值 
{
	if(qu_r[hao]==0 || qu_l[hao]==0)
	{
		return ;
	}
	if(qu_l[hao]==h && qu_r[hao]==h) 
	{
		fak[hao]=zhi;
		return ;
	}
	if(qu_l[hao]<=h && h<=qu_r[hao])
	{
		fix(hao*2,h,zhi);
		fix(hao*2+1,h,zhi);
		fak[hao]=max(fak[hao],zhi); 
		return ;
	}
	else
	{
		return ;
	}
} 

我们只需要

fix(1,[改变的位置],[改变的值]);

就完事了。

4. 区间查询

说了这么多,这个树依然没有体现出它巨大的作用。

那么,我们来输出区间最大值吧。我们继续用老图。

M3uMOx.png

如果全包含,那么我们就分三种情况(我在这里被坑死了)

如果这个区间被左区间全包含,则走左边。

如果这个区间被右区间全包含,则走右边。

如果这个区间被两个区间都包含一些,那么切成两段求max。

int find(int l,int r,int hao)
{
	if(l>r)
	{
		return 0;
	}
	if(qu_l[hao]<=l && r<=qu_r[hao])
	{
		if(qu_l[hao]==l && r==qu_r[hao])
		{
			return fak[hao];
		}
		else
		{
			if(qu_l[hao]==qu_r[hao])
			{
				return fak[hao];
			}
			else
			{
				if(qu_l[hao*2]<=l && r<=qu_r[hao*2])
				{
					return find(l,r,hao*2);
				}
				if(qu_l[hao*2+1]<=l && r<=qu_r[hao*2+1])
				{
					return find(l,r,hao*2+1);
				}
				
				return max(find(l,qu_r[hao*2],hao*2),find(qu_l[hao*2+1],r,hao*2+1));
			}
		}
	}
	else
	{
		return 0;
	}
} 

如果我们要求o到oo的最大值,那么

find(o,oo,1)

就是它的值。

5. 完成代码

输入格式:

先输入一个n,然后以后n个数输入原数组的值。

然后输入o,然后o行输入两个数oo,ooo,表示把oo位的数修改为ooo。

然后输入p,然后p行输入两个数pp,ppp,表示需要输出pp到ppp的最大值。

#include <cstdio>
int zhi[10001];//原数组 

int fak[10001];//线段树数组 
int fa[10001];//父亲节点 未使用 // 
int left[10001];//左儿子编号 未使用 // 
int qu_l[10001];//维护的区间 左范围   
int right[10001]; //右儿子编号  未使用 // 
int qu_r[10001];//维护的区间 右范围 

int max(int o,int p)
{
	if(o>=p)
	{
		return o;
	}
	else
	{
		return p;
	}
}


void in(int l,int r,int hao)//左,右,编号 
{
	if(l==r)
	{
		qu_l[hao]=l;
		qu_r[hao]=r;//忘记记录害死猫 
		fak[hao]=zhi[l];
		return ;
	}
	
	in(l,(l+r)/2,hao*2);
	in((l+r)/2+1,r,hao*2+1);
	
	fak[hao]=max(fak[hao*2],fak[hao*2+1]); 
	
	qu_l[hao]=l;
	qu_r[hao]=r;//记录其维护的区间 
	return ;
}

void fix(int hao,int h,int zhi)//线段树所在编号  单点修改需要的编号   修改的值 
{
	if(qu_r[hao]==0 || qu_l[hao]==0)
	{
		return ;
	}
	if(qu_l[hao]==h && qu_r[hao]==h) 
	{
		fak[hao]=zhi;
		return ;
	}
	if(qu_l[hao]<=h && h<=qu_r[hao])
	{
		fix(hao*2,h,zhi);
		fix(hao*2+1,h,zhi);
		fak[hao]=max(fak[hao],zhi); 
		return ;
	}
	else
	{
		return ;
	}
} 
int find(int l,int r,int hao)
{
	if(l>r)
	{
		return 0;
	}
	if(qu_l[hao]<=l && r<=qu_r[hao])
	{
		if(qu_l[hao]==l && r==qu_r[hao])
		{
			return fak[hao];
		}
		else
		{
			if(qu_l[hao]==qu_r[hao])
			{
				return fak[hao];
			}
			else
			{
				if(qu_l[hao*2]<=l && r<=qu_r[hao*2])
				{
					return find(l,r,hao*2);
				}
				if(qu_l[hao*2+1]<=l && r<=qu_r[hao*2+1])
				{
					return find(l,r,hao*2+1);
				}
				
				return max(find(l,qu_r[hao*2],hao*2),find(qu_l[hao*2+1],r,hao*2+1));
			}
		}
	}
	else
	{
		return 0;
	}
} 
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&zhi[i]);
	}
	
	in(1,n,1); 
	
	int ooo;
	scanf("%d",&ooo);
	for(int i=1;i<=ooo;i++)
	{
		int o,oo;
		scanf("%d %d",&o,&oo);
		fix(1,o,oo);
	} 
	
	scanf("%d",&ooo);
	for(int i=1;i<=ooo;i++)
	{
		int o,oo;
		scanf("%d %d",&o,&oo);
		printf("%d\n",find(o,oo,1));
	} 
	
	return 0;
} 

6. 其他问题

如果想要区间修改等事项,请学习可持久化线段树。