9.2 线段树详解+模板代码

线段树目录

  • 一、线段树介绍
    • 基本结构
  • 二、建树
  • 三、线段树的单点修改
  • 四、线段树的区间查询
  • 五、线段树的区间修改+查询
    • 线段树的区间修改与懒惰标记
  • 六、例题


一、线段树介绍

线段树**(Segment Tree)是一种基于分治思想的二叉树结构**,常用来维护 区间信息 的数据结构,可以在 O (log N)的时间复杂度内实现==单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)==等操作

基本结构

  • 线段树的每个节点代表一个区间
  • 线段树具有唯一的根节点,代表的区间是整个统计范围,eg.[ 1 , N ]
  • 线段树每个叶节点都代表一个长度为1的区间[ x ,x]
  • 对于每个内部节点[ l, r],它的左子节点是[ l, mid] , 右子节点是[mid+1, r],其中,mid=(l+r)/2 (向下取整)

节点编号
根结点编号为1;
编号为 x 的节点的左孩子节点编号为 2x ,右孩子节点编号为 2x+1 .
因此,可以使用一个struct 数组保存线段树。通常,N个节点的满二叉树 有
N+N/2+N/4+…+2+1=2N - 1个节点,但是线段树最后一层会有空余,所以,存储线段树的数组规格 >= 4N,以保证不越界

二、建树

给定一个长度为 N 的序列 A,便可以在区间 [ 1, N]上建一棵线段树,每个叶节点 [ i,i ] 存储 A[ i ]的值。线段树的二叉树结构可以很方便地从下向上传递信息

递归建树
根节点管辖的区间长度已经是1,则可以直接根据a数组上相应位置的值初始化该节点。
否则将该区间从中点处分割为两个子区间,分别进入左右子节点递归建树最后合并两个子节点的信息

/**/
void build(int s, int t, int p) {// 对[s,t]区间建立线段树,当前区间节点编号为 p
	  if (s == t) {
	    d[p] = a[s];
	    return;
	  }
	  int m = s + ((t - s) >> 1);
	  // 移位运算符的优先级小于加减法,所以加上括号
	  // 如果写成 (s + t) >> 1 可能会超出 int 范围
	  build(s, m, p * 2), build(m + 1, t, p * 2 + 1);
	  // 递归对左右区间建树
	  d[p] = d[p * 2] + d[(p * 2) + 1];
}

三、线段树的单点修改

/*以区间最大值问题为例*/
struct SegmentTree{
	int l,r;
	int dat;//区间最大值 
}d[size*4];

//单点修改 
void change(int x,int v,int p){//把 a[ x ]的值修改为 v 
	if(d[p].l==d[p].r){//找到叶节点 
		d[p].dat=v;
		return;
	}
	int mid=(d[p].l+d[p].r)/2;
	if(x<=mid) change(x,v,p*2);//x 属于左半区间 
	else change(x,v,p*2+1);//x 属于右半区间 
	d[p].dat=max(d[p*2].dat,d[p*2+1].dat);//从下往上更新信息 
} 

注意,在线段树中,根节点(编号为1的节点)是执行各个指令的入口,因此,需要从根节点出发进行操作。即
建树的调用入口:build (1, n, 1);
单点修改的调用入口:change(x, v, 1);

四、线段树的区间查询

/*以区间最大值问题为例*/
struct SegmentTree{
	int l,r;
	int dat;//区间最大值 
}d[size*4];

//区间查询 
int ask(int l,int r,int p) {//查询序列 a 在区间 [l,r] 上的最大值  
	if(l<=d[p].l&&d[p].r<=r){//完全包含 
		return d[p].dat;
	}
	int mid(d[p].l+d[p].r)/2;
	int val=-(1<<30);//负无穷大 
	if(l<=mid) val=max(val,ask(l,r,p*2));//左子节点有重叠 
	if(r>mid) val=max(val,ask(l,r,p*2+1));//右子节点有重叠 
	return val;
}

//调用入口 ask(l,r,1) 

执行区间查询时,从根节点开始,递归查询
有以下三种情况:
1.要查询的 区间 [l,r] 完全包含当前编号p代表的区间 ,直接返回
2.l<=mid 左子节点有重叠,递归访问左子节点
3.r>mid 右子节点有重叠,递归访问右子节点

注意,l,r都位于 mid 的一侧时,只会递归一个子树
当 == l,r位于mid两侧时 ,才会同时递归两棵子树,且这种情况只会出现一次 ==

五、线段树的区间修改+查询

线段树的区间修改与懒惰标记

在线段树区间查询中,当查询的 区间 [l,r] 完全包含当前编号p代表的区间时,直接返回存储的答案。在“区间修改”中,若某个节点被修改区间 [l,r] 完全覆盖,那么以该节点为根的整个子树的所有节点存储的信息都会发生变化,逐一修改,时间复杂度将会变为 O(N),并且如果之后的查询用不到此次修改的值,那么更新该节点的子树是毫无意义的。这时,选择在执行修改指令时,如果某个节点p代表的区间被修改区间 [l,r] 完全覆盖,就在节点p上添加一个懒标记,表示“该节点已经被修改,但其子节点还没有被更新”,这样仍能保证O (log N)的时间复杂度

六、例题

《算法进阶指南》例题: 245. 你能回答这些问题吗
下面代码思路基本有了,但不能输入 QWQ

#include
using namespace std;
const int N=500005;
int n,m,a[N];
struct node{
	//sum区间和,dat区间最大连续字段和
	//lmax,rmax仅靠左,右端的最大连续字段和 
	int lmax,rmax,sum,dat,l,r;
}t[N*4];
void build(int l,int r,int p){
	if(t[p].l==l&&t[p].r==r){
		t[p].lmax=t[p].rmax=t[p].sum=t[p].dat=a[l];
		return;
	} 
	int mid=(l+r)>>1;
	build(l,mid,p*2);
	build(mid+1,r,p*2+1);
	
	t[p].sum=t[p*2].sum+t[p*2+1].sum;
	t[p].lmax=max(t[p*2].lmax,t[p*2].sum+t[p*2+1].lmax);
	t[p].rmax=max(t[p*2+1].rmax,t[p*2+1].sum+t[p*2].rmax);
	t[p].dat=max(max(t[p*2].dat,t[p*2+1].dat),t[p*2].rmax+t[p*2+1].lmax);
}
//单点修改 
/*void change(int x,int v,int p){
	if(t[p].l==x&&t[p].r==x){
		t[p].lmax=v;t[p].rmax=v;
		t[p].sum=v;t[p].dat=v;
		return;
	}
	int mid=t[p].l+(t[p].r-t[p].l)>>1;
	if(x<=mid) change(x,v,p*2);
	else change(x,v,p*2+1);
	
	t[p].sum=t[p*2].sum+t[p*2+1].sum;
	t[p].lmax=max(t[p*2].lmax,t[p*2].sum+t[p*2+1].lmax);
	t[p].rmax=max(t[p*2+1].rmax,t[p*2+1].sum+t[p*2].rmax);
	t[p].dat=max(t[p*2].dat,t[p*2+1].dat,t[p*2].rmax+t[p*2+1].lmax);
}*/
int change(int x,int v,int p)
{
    if (t[p].l==t[p].r)
    {
        t[p].dat=v;
        t[p].sum=v;
        t[p].lmax=v;
        t[p].rmax=v;
        return 0;
    }
    int mid=(t[p].l+t[p].r)>>1;
    if (x<=mid)
        change(x,v,p<<1);
    else
        change(x,v,p*2+1);
    t[p].sum=t[p<<1].sum+t[p*2+1].sum;
    t[p].lmax=max(t[p<<1].lmax,t[p<<1].sum+t[p*2+1].lmax);
    t[p].rmax=max(t[p*2+1].rmax,t[p*2+1].sum+t[p<<1].rmax);
    t[p].dat=max(max(t[p<<1].dat,t[p*2+1].dat),t[p<<1].rmax+t[p*2+1].lmax);
}
//区间查询 
//注意要查询的是区间最大连续字段和,所以返回结构体 
node ask(int l,int r,int p){
	if(l<=t[p].l&&r>=t[p].r){
		return t[p];
	}
	int mid=t[p].l+(t[p].r-t[p].l)>>1;
	if(r<=mid) return ask(l,r,p*2);
	else if(l>mid) return ask(l,r,p*2+1);
	else{//要查询的区间跨过mid
		node a,b,c;//a,b分别为左、右儿子的最大子段和,c为跨过mid的最大子段和
		a=ask(l,mid,p*2);b=ask(mid+1,r,p*2+1);
		c.l=l,c.r=r;
		c.sum=a.sum+b.sum;
		c.dat=max(max(a.dat,b.dat),a.rmax+b.lmax);
		c.lmax=max(a.lmax,a.sum+b.lmax);
		c.rmax=max(b.rmax,a.rmax+b.sum);
		return c; 
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	build(1,n,1);
	while(m--){
		int k,x,y;
		cin>>k>>x>>y;
		if(k==1) {
			if(x>y) swap(x,y);
			cout<<ask(x,y,1).dat<<endl;
		}
		else change(x,y,1);	
	}
}

你可能感兴趣的:(算法打卡学习,数据结构,算法,c++)