线段树的原理及实现

hdu 1754

本题的意思是 单点更新和区间查询最大值

现在我们就用这题来理解一下线段树;

线段树的原理

对于线段树的具体解释本人就不多说了;下面附上网上大佬的解释,很详细,大家可以先去看下再来看代码的具体实现;

链接1

链接2

下面主要讲解代码;

对于线段树,我们首先要做的是如何建立一个线段树的框架;

基本结构

struct NODE{
	int left,right,value;
}node[maxnnode];//建立一个结构体用于存区间;
int father[maxn];//用father数组记录每个点的下标; 

我们用结构体存入区间 ,left和right代表着节点的区间;

用father数组存入叶子节点在线段树中的位置;

建树

我们采用顺序存储 左孩子=父亲*2 右孩子=父亲*2+1;

下面附上大佬的一张图,以便于理解;

 下面给你区间 1-5的数组  分别是 1 2 3 4 5

                                                        [1,5]1
                            [1,3]  2                              [4,5]   3            
             4  [1,2]           [3,3]  5              [4,4] 6               [5,5]7
  8【1,1】 9【2,2】  10     11          12     13              14     15

而叶子节点代表的就是数字本身  如12号 区间【3,3】 就是原始数据中的3号

在本题中 每个节点代表的是该区间的最大值(有的题也可以代表区间和)

所以 各个叶子节点的值就是 所代表的节点的值

而father数组则是用来记录原始数据中各个点在线段树中对应的叶子节点的编号  如  father【3】=5;

void BuildTree(int i,int left,int right)
{
	node[i].left =left;//i是各个节点在线段树中的位置编号
	node[i].right =right;
	node[i].value =0;
	if(left==right)//到了叶子节点
	{
		father[left]=i;//left的下标为i 
		return ;
	}//当区间距离为0时,结束递归;
	BuildTree(i<<1,left,(left+right)/2);
	BuildTree((i<<1)+1,(left+right)/2+1,right); 
 } 

单点更新

单点更新是自下而上的

比如说 我们要把 1 2 3 4 5 中的3 更新为6

那么 首先 我们通过刚才的father数组可以知道  3 存在线段树中的5号位置  并且叶子节点代表的区间就是一个点

所以我们直接  使 node[ father[ 3 ] ].value=6  就修改了3号的值

然后  我们通过 5/2   得到了 他的父亲节点2 而通过2*2 和2*2+1  得到了4 和5 更新后的两个孩子节点

取两个中的最大值  则完成了2号节点的更新  以此类推 向上更新 直到根节点
 

void Query(int i,int l,int r)
 {
 	if(l==node[i].left&&r==node[i].right  )
 	{
 		//MAX=MAX<node[i].value ?node[i].value:MAX;
 		MAX=max(MAX,node[i].value );
 		return ;
	 }
 	i=i<<1;//判断左孩子; 
 	if(l<=node[i].right)//涉及左孩子 
 	{
 		if(r<=node[i].right )//完全再区间内; 
 		Query(i,l,r);
 		else//不完全在; 
 		Query(i,l,node[i].right );
	 }
	 i++;//判断右孩子; 
	 if(r>=node[i].left)//涉及右孩子; 
	 {
	 	if(l>=node[i].left )//完全在右孩子里; 
		Query(i,l,r);
		else//不完全在有孩子里; 
		Query(i,node[i].left,r );
	 }
  } 

下面是hdu1754题解代码

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxnnode=1e6+7;
const int maxn=1e6+5;
int MAX=0;
struct NODE{
	int left,right,value;
}node[maxnnode];
int father[maxn];//记录每个点的下标; 
void BuildTree(int i,int left,int right)
{
	node[i].left =left;
	node[i].right =right;
	node[i].value =0;
	if(left==right)
	{
		father[left]=i;//left的下标为i 
		return ;
	}//当区间距离为0时,结束递归;
	BuildTree(i<<1,left,(left+right)/2);
	BuildTree((i<<1)+1,(left+right)/2+1,right); 
 } 
 void UpdateTree(int ri)
 {
 	if(ri==1)return ;
 	int fi=ri/2;//ri的父亲节点 
 	int a=node[fi<<1].value ;//a,b为父亲节点的两个子节点; 
 	int b=node[(fi<<1)+1].value ;
 	node[fi].value =max(a,b);//更新父亲节点,里面存放子节点的最大值; 
 	UpdateTree(ri/2);//逐步向上更新; 
 }
 void Query(int i,int l,int r)
 {
 	if(l==node[i].left&&r==node[i].right  )
 	{
 		//MAX=MAX<node[i].value ?node[i].value:MAX;
 		MAX=max(MAX,node[i].value );
 		return ;
	 }
 	i=i<<1;//判断左孩子; 
 	if(l<=node[i].right)//涉及左孩子 
 	{
 		if(r<=node[i].right )//完全再区间内; 
 		Query(i,l,r);
 		else//不完全在; 
 		Query(i,l,node[i].right );
	 }
	 i++;//判断右孩子; 
	 if(r>=node[i].left)//涉及右孩子; 
	 {
	 	if(l>=node[i].left )//完全在右孩子里; 
		Query(i,l,r);
		else//不完全在有孩子里; 
		Query(i,node[i].left,r );
	 }
  } 
int main()
{
	int m,n,g;
	while(~scanf("%d%d",&n,&m))
	{
		BuildTree(1,1,n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&g);
			node[father[i]].value =g;
			UpdateTree(father[i]);
		}
		while(m--)
		{
			char op[10];
			int x,y;
			scanf("%s%d%d",op,&x,&y);
			if(op[0]=='Q')
			{
				MAX=0;
				Query(1,x,y);
				printf("%d\n",MAX);
			}
			else
			{
				node[father[x]].value =y;
				UpdateTree(father[x]);
			}
		}
    }
} 

猜你喜欢

转载自blog.csdn.net/qq_42140101/article/details/84189724