1. 程式人生 > 實用技巧 >線段樹學習筆記

線段樹學習筆記

線段樹學習總結

概念

每個節點以結構體的方式儲存,結構體包含以下幾個資訊:
區間左端點、右端點;(這兩者必有)
這個區間要維護的資訊(事實際情況而定,數目不等)。
圖例:

性質

1、每個節點的左孩子區間範圍為[l,mid],右孩子為[mid+1,r]

2、對於結點k,左孩子結點為2k,右孩子為2k+1,這符合完全二叉樹的性質

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

建樹

基本結構:

a.對於二分到的每一個結點,給它的左右端點確定範圍。

b.如果是葉子節點,儲存要維護的資訊。

c.累加。

參考程式碼

struct node{
	int l,r,w;
}tree[100010];
inline void Build(int l,int r,int k){
	tree[k].l = l,tree[k].r = r;
	if(l == r){
		scanf("%d",&tree[k].w);
		return;
	}
	int mid = (tree[k].l + tree[k].r) / 2;
	Build(l,mid,k * 2);//遞迴
	Build(mid + 1,r,k * 2 + 1);//遞迴
	tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;//累加
	return;
}

注意事項

1.開結構體陣列時要開4倍

2.在輸入完葉子結點後要return,因為葉子結點不用再遞迴了。

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

單點查詢

基本結構:

與二分查詢法基本一致,如果當前列舉的點左右端點相等,即葉子節點,就是目標節點。否則設查詢位置為x,當前結點區間範圍為l,r,中點為mid。則如果x<=mid,就遞迴它的左孩子,否則遞迴它的右孩子

參考程式碼

inline int Ask_p(int x){
	if(tree[x].l == tree[x].r){//葉子結點
		return tree[x].w;
	}
	int mid = (tree[x].l + tree[x].r) / 2;
	if(mid >= x)
		Ask_p(x * 2);//目標位置比中點靠左,就遞迴左孩子 
	else Ask_p(x * 2 + 1);//遞迴右孩子
}

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

單點修改

基本結構:

用單點查詢的方法找到目標節點並修改它的權值

參考程式碼

inline void change(int x,int y,int k){
	if(tree[k].l == tree[k].r){//找到目標節點
		tree[k].w = y;//xiu'gai'quan'zhi
		return;
	}
	int mid = (tree[k].l + tree[k].r) / 2;
	if(mid >= x)
		change(x,y,k * 2);
	else change(x,y,k * 2 + 1);
	tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
	return;
} 

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

區間查詢

基本結構:

分情況來討論:

假設l,r為節點區間,x,y為目標區間,則有一下三種情況:

一:

二:

三:

參考程式碼

inline int Ask_section(int x,int y,int k){
	if(tree[k].l > y || tree[k].r < x)return 0; 
	if(tree[k].l >= x && tree[k].r <= y){
		return tree[k].w;
	}
	int mid = (tree[k].l + tree[k].r) / 2;
	return Ask_section(x,y,k * 2) + Ask_section(x,y,k * 2 + 1); 
}

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

完整程式碼在此(乘法和加法)

#include <bits/stdc++.h>
using namespace std;
struct node{
	long long w,l,r;
	long long tag_add = 0,tag_mul = 1;
}tree[600010];    //4 times;
long long ans,n,m,p;
void build(long long l,long long r,long long num){
	tree[num].l = l;tree[num].r = r;
	tree[num].tag_mul = 1;
	if(tree[num].l == tree[num].r){
		cin>>tree[num].w;
		tree[num].w %= p;
		return;
	}
	long long mid = (tree[num].l + tree[num].r) / 2;
	build(l,mid,num * 2);
	build(mid + 1,r,num * 2 + 1);
	tree[num].w = (tree[num * 2].w + tree[num * 2 + 1].w) % p;
	return;
}
void pushdown(long long num){
	tree[num << 1].w = (tree[num << 1].w * tree[num].tag_mul + tree[num].tag_add * (tree[num << 1].r - tree[num << 1].l + 1)) % p;
	tree[num << 1 | 1].w = (tree[num << 1 | 1].w * tree[num].tag_mul + tree[num].tag_add * (tree[num << 1 | 1].r - tree[num << 1 | 1].l + 1)) % p;
	tree[num << 1].tag_mul *= tree[num].tag_mul;
	tree[num << 1].tag_mul %= p;
	tree[num << 1 | 1].tag_mul *= tree[num].tag_mul;
	tree[num << 1 | 1].tag_mul %= p;
	tree[num << 1].tag_add = (tree[num << 1].tag_add * tree[num].tag_mul + tree[num].tag_add) % p;
	tree[num << 1 | 1].tag_add = (tree[num << 1 | 1].tag_add * tree[num].tag_mul + tree[num].tag_add) % p;
	tree[num].tag_mul = 1;
	tree[num].tag_add = 0;
	return;
}
void ask_a(long long num,long long tar_l,long long tar_r){
	if(tree[num].l >= tar_l && tree[num].r <= tar_r){
		ans += tree[num].w;
		ans %= p;
		return;
	}
	pushdown(num);
	long long mid = (tree[num].l + tree[num].r) / 2;
	if(mid >= tar_l)	
		ask_a(num * 2,tar_l,tar_r);
	if(mid < tar_r)
		ask_a(num * 2 + 1,tar_l,tar_r);
}
void add_a_mul(long long num,long long tar_l,long long tar_r,long long w){
	if(tree[num].l >= tar_l && tree[num].r <= tar_r){
		tree[num].tag_mul *= w;
		tree[num].tag_mul %= p;
		tree[num].w *= w;
		tree[num].w %= p;
		tree[num].tag_add *= w;
		tree[num].tag_add %= p;
		return;
	}
	pushdown(num);
	long long mid = (tree[num].l + tree[num].r) / 2;
	if(mid >= tar_l)
		add_a_mul(num * 2,tar_l,tar_r,w);
	if(mid < tar_r)
		add_a_mul(num * 2 + 1,tar_l,tar_r,w);
	tree[num].w = (tree[num * 2].w + tree[num * 2 + 1].w) % p;
}
void add_a_add(long long num,long long tar_l,long long tar_r,long long w){
	if(tree[num].l >= tar_l && tree[num].r <= tar_r){
		tree[num].w += (tree[num].r - tree[num].l + 1) * w;
		tree[num].w %= p;
		tree[num].tag_add += w;
		tree[num].tag_add %= p;
		return;
	}
	pushdown(num);
	long long mid = (tree[num].l + tree[num].r) / 2;
	if(mid >= tar_l)
		add_a_add(num * 2,tar_l,tar_r,w);
	if(mid < tar_r)
		add_a_add(num * 2 + 1,tar_l,tar_r,w);
	tree[num].w = (tree[num * 2].w + tree[num * 2 + 1].w) % p;
}
int main(){
	cin>>n>>m>>p;
	build(1,n,1);
	for(int i = 1;i <= m;i++){
		int opt;
		cin>>opt;
		if(opt == 1){
			int x,y,w;
			cin>>x>>y>>w;
			add_a_mul(1,x,y,w);
		}
		if(opt == 2){
			int x,y,w;
			cin>>x>>y>>w;
			add_a_add(1,x,y,w);
		}
		if(opt == 3){
			int x,y;
			cin>>x>>y;
			ans = 0;
			ask_a(1,x,y);
			ans %= p;
			cout<<ans<<endl;
		}
	}
	return 0;
}

小結

線段樹真是個好東西

參考來源

未完待續·········