首页 > 其他分享 >H - 线段树 1【GDUT_22级寒假训练专题五】

H - 线段树 1【GDUT_22级寒假训练专题五】

时间:2023-02-20 23:22:46浏览次数:57  
标签:GDUT 22 int 线段 tr 结点 区间 include

H - 线段树 1

原题链接

题意

区间修改区间查询

线段树简介

线段树是一颗二叉树,他能通过树的分支将一块区间划分为数个单元区间,每个单元区间对应线段树中的一个叶结点
如图,一颗具有15个结点的完全二叉树就可以用于划分一块 [1,8] 的区间为单元区间。
image

  • 对于线段树中的一个结点:
    左儿子表示区间:\([L,(L+R)/2]\)
    右儿子表示区间:\([(L+R)/2+1,R]\)
  • 对于节点编号采用顺序编号
    左儿子编号:\(2x\)
    右儿子编号:\(2x+1\)
    即由上到下,由左到右进行编号

作用

线段树主要用于维护区间信息,支持区间加,减,乘,取模等操作

代码分析

定义一个结构体节点,每个结点都具有四个值,lazy值、存储值、区间左右边界,定义一个结构体便于表示及存储

struct node{
	int l,r;	//l,r为该节点表示的区间
	LL lz,pre;	//lz代表这个点的lazy值(lazy标记功能是对暂时不需要的区间进行延迟累积更新,达到优化的目的),pre代表这个点储存的值(区间和)
}tr[N * 4];	//经验值,枚举区间长度从 1−10^6,最多有2n个叶子节点,2n+n+n/2+... < 4n,则所用到的节点数最多是 4n

接下来就是建树的过程
左儿子编号:\(2p\) 左儿子表示区间:\([L,(L+R)/2]\)
右儿子编号:\(2p+1\) 右儿子表示区间:\([(L+R)/2+1,R]\)

//初始化维护线段树
void buildtree(int p,int l,int r){	//递归建树,表示p点储存的区间为[l,r]
	tr[p] = {l,r};
	if(l == r){		//递归到区间只剩一个节点时赋值
		tr[p].pre = a[r];
		return;
	}
	int mid = tr[p].l + tr[p].r >> 1;	//划分左右儿子	 	l+r )>>1 即 (l+r)/2
	buildtree(p << 1,l,mid);	//递归建左树				p<<1  即   p*2
	buildtree(p << 1 | 1,mid + 1,r);	//递归建右树				p<<1|1  即  p*2+1(注意偶数|1才可以视为+1)
	tr[p].pre = tr[p << 1].pre + tr[p << 1 | 1].pre;	//递归到底层后用子节点实现对父节点的更新(左右儿子之和)
}

更新操作:

void update(int p,int l,int r,int x){
	if(tr[p].l >= l && tr[p].r <= r){	//如果需要更新的区间完全覆盖节点区间
		tr[p].pre += (LL)x * len(p);	//加上整个节点长度的x
		tr[p].lz += x; 		//维护lazy标记
		return;		//由于已覆盖该节点,其子节点区间只会是该节点的子集,因此可以利用lazy标记,后续需要使用更小的区间时再进行更新
	}	//更新完全覆盖
	lazy(p);	//如果需要更新的区间没有完全覆盖节点区间,下传lazy值(因为现在要用到更小的区间)
	int mid = tr[p].l + tr[p].r >> 1;		//二分
	if(l <= mid)update(p << 1,l,r,x);		//如果需要更新的区间涉及左节点,那么递归下去继续更新
	if(r > mid)update(p << 1 | 1,l,r,x);	//如果需要更新的区间涉及右节点,那么递归下去继续更新
	tr[p].pre = tr[p << 1].pre + tr[p << 1 | 1].pre;	//由于左右节点已经被更新,那么父节点也需要更新
}

示例:
image
将 \([1,4]\) 加\(1\),则结点\(1\)打上懒标记\(1\)
将 \([1,3]\) 加\(1\),此时懒标记需要下传
结点\(1\)懒标记清空
结点\(2\),结点3打上懒标记\(1\),同时将值修改
下传后再处理\([1,3]\)加\(1\)
将 \([1,3]\) 加\(1\),则结点\(2\)的懒标记再加上\(1\),接点\(6\)值直接加\(1\)

可见一个点的懒标记值与点的值没有关系,仅影响子节点的值,懒标记存在则子节点值需要修改

下传lazy操作:

void lazy(int p){
	if(tr[p].lz){
		tr[p << 1].pre += tr[p].lz * len(p << 1);	//左节点更新储存值
		tr[p << 1 | 1].pre += tr[p].lz * len(p << 1 | 1);	//右节点更新储存值
		tr[p << 1].lz += tr[p].lz;		//传递lazy值给左节点
		tr[p << 1 | 1].lz += tr[p].lz;		//传递lazy值给右节点
		tr[p].lz = 0;	//清空当前节点lazy值
	} 
}

输出节点长度:

int len(int p){
	return tr[p].r - tr[p].l + 1;
}

输出区间和:

LL query(int p,int l,int r){
	if(tr[p].l >= l && tr[p].r <= r){	//如果询问的区间完全覆盖节点区间
		return tr[p].pre;	//返回节点值(部分区间和)
	}
	lazy(p);	//否则就下传lazy值
	LL ans = 0;
	int mid = tr[p].l + tr[p].r >> 1;	//递归寻找所有适配区间
	if(l <= mid)ans += query(p << 1,l,r);	
	if(r > mid)ans += query(p << 1 | 1,l,r);
	return ans;		//返回答案
}

最终代码

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;

#define X first
#define Y second

typedef pair<int,int> pii;
typedef long long LL;
const char nl = '\n';
const int N = 1e6+10;
const int M = 2e5+10;
int n,m;
int a[N];

struct node{
	int l,r;
	LL lz,pre;
}tr[N * 4];

void buildtree(int p,int l,int r){
	tr[p] = {l,r};
	if(l == r){
		tr[p].pre = a[r];
		return;
	}
	int mid = tr[p].l + tr[p].r >> 1;
	buildtree(p << 1,l,mid);
	buildtree(p << 1 | 1,mid + 1,r);
	tr[p].pre = tr[p << 1].pre + tr[p << 1 | 1].pre;
}

int len(int p){
	return tr[p].r - tr[p].l + 1;
}

void lazy(int p){
	if(tr[p].lz){
		tr[p << 1].pre += tr[p].lz * len(p << 1);
		tr[p << 1 | 1].pre += tr[p].lz * len(p << 1 | 1);
		tr[p << 1].lz += tr[p].lz;
		tr[p << 1 | 1].lz += tr[p].lz;
		tr[p].lz = 0;
	} 
}

void update(int p,int l,int r,int x){
	if(tr[p].l >= l && tr[p].r <= r){
		tr[p].pre += (LL)x * len(p);
		tr[p].lz += x; 
		return;
	}	//更新完全覆盖
	lazy(p);
	int mid = tr[p].l + tr[p].r >> 1;
	if(l <= mid)update(p << 1,l,r,x);
	if(r > mid)update(p << 1 | 1,l,r,x);
	tr[p].pre = tr[p << 1].pre + tr[p << 1 | 1].pre;
}

LL query(int p,int l,int r){
	if(tr[p].l >= l && tr[p].r <= r){
		return tr[p].pre;
	}
	lazy(p);
	LL ans = 0;
	int mid = tr[p].l + tr[p].r >> 1;
	if(l <= mid)ans += query(p << 1,l,r);
	if(r > mid)ans += query(p << 1 | 1,l,r);
	return ans;
}

void solve(){
	cin >> n >> m;
	for(int i = 1; i <= n; i ++ )cin >> a[i];
	buildtree(1,1,n);
	while(m -- ){
		int op,l,r,x;
		cin >> op >> l >> r;
		if(op == 1){
			cin >> x;
			update(1,l,r,x);
		}
		else{
			cout << query(1,l,r) << nl;
		}
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);

	solve();
}

标签:GDUT,22,int,线段,tr,结点,区间,include
From: https://www.cnblogs.com/J-12045/p/17139396.html

相关文章