線段樹

參考資料:https://www.cnblogs.com/TheRoadToTheGold/p/6254255.html     

作者:xxy

 

感謝GAY神仙的講解和毒瘤代碼資瓷以及HMR神仙的優美的代碼

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。——來自百度百科
 
線段樹的基本操作:建樹 單點修改 單點查詢 區間修改 區間查詢 

 

由此圖可知,每個區間左兒子包含區間[l,mid],右兒子包含區間[mid+1,r]     節點k的左兒子是(k<<1),右兒子是(k<<1+1)

而且兩倍空間明顯不夠用,所以開四倍就好了,我也不知道為什麼_(:з」∠)_據說可以畫個[1,10]的圖試試看。

1、建樹

二分需要修改的區間,如果有兒子就遞歸繼續修改,如果沒有就合併自己的兒子。

void build(int k,int ll,int rr){
	tree[k].l=ll,tree[k].r=rr; 
	if(tree[k].l==tree[k].r){
		tree[k].w=f[ll];
		return ;
	}
	int m=((ll+rr)>>1),lls=(k<<1),rrs=((k<<1)|1);
	build(lls,ll,m),build(rrs,m+1,rr);
	tree[k].w=tree[lls].w+tree[rrs].w;
}

  

2·3、單點修改 單點查詢

先二分找到需要修改的點,然後往上一層一層地修改/查詢。

void cpoint(int k){
	if(tree[k].l==tree[k].r){
		tree[k].w+=z;
		return ;
	}
	if(tree[k].lz) down(k);
	int m=((tree[k].l+tree[k].r)>>1),lls=(k<<1),rrs=((k<<1)|1);
	if(x<=m) cpoint(lls);
	else cpoint(rrs);
	tree[k].w=tree[lls].w+tree[rrs].w;
} 
void apoint(int k){
	if(tree[k].l==tree[k].r){
		ans=tree[k].w;
		return ;
	}
	if(tree[k].lz) down(k);
	int m=((tree[k].l+tree[k].r)>>1);
	if(x<=m) apoint((k<<1));
	else apoint((k<<1)+1);
}

  

4·5、區間修改 區間查詢

二分找到需要修改/查詢的區間或子區間,再依次向上修改/查詢更大的區間。

void cinterval(int k){
	if(tree[k].l>=x&&tree[k].r<=y){
		tree[k].w+=(tree[k].r-tree[k].l+1)*z;
		tree[k].lz+=z;
		return ;
	}
	if(tree[k].lz) down(k);
	int m=((tree[k].l+tree[k].r)>>1),lls=(k<<1),rrs=((k<<1)|1);
	if(x<=m) cinterval(lls);
	if(y>m) cinterval(rrs);
	tree[k].w=tree[lls].w+tree[rrs].w;
}
void ainterval(int k){ 
	if(tree[k].l>=x&&tree[k].r<=y){
		ans+=tree[k].w;
		return ;
	}
	if(tree[k].lz) down(k);
	int m=((tree[k].l+tree[k].r)>>1);
	if(x<=m) ainterval((k<<1));
	if(y>m) ainterval((k<<1)+1);
}

  

以上是線段樹的基本操作。

//當然單點修改也可以用區間修改的函數啦,只要把左右端點設成一樣的就好了。

為了節約時間,我們引入Lazy標記——給每個節點打標記,需要用到該區間時再下放標記。即“修改的時候只修改對查詢有用的點”。

所以每次修改/查詢的時候,都要先下放標記再進行其他操作。

這個模板的修改都只是加法的,由加法結合率可知,這裡的標記可以保存很久再下放。     

如果同時進行加法和乘法的修改,開兩個標記就好啦,不過乘法的標記要優先下放/否則會對以後的加法造成影響qwq。

·下傳標記

每次修改/查詢的時候都要檢查當前區間有無標記(如果標記只有加法,什麼時候下放都可以;如果有乘法,那麼當前區間就會對後面區間造成影響,所以這種標記最多只能存一次),如果有就下放——把當前標記下放給自己的兩個兒子,修改兒子的值,再把當前的標記清零(防止重複計算)。

void down(int k){ 
	int lls=(k<<1),rrs=((k<<1)|1);
	tree[lls].lz+=tree[k].lz,tree[rrs].lz+=tree[k].lz;
	tree[lls].w+=tree[k].lz*(tree[lls].r-tree[lls].l+1);
	tree[rrs].w+=tree[k].lz*(tree[rrs].r-tree[rrs].l+1);
	tree[k].lz=0;
}

  

 大概就是這樣,以下是Luogu【P3372】【模板】線段樹1 的代碼//當然有很大一部分與本題無關qwq

题目描述

如题,已知一个数列,你需要进行下面两种操作:

1.将某区间每一个数加上x

2.求出某区间每一个数的和

输入输出格式

输入格式:

 

第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。

第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

接下来M行每行包含3或4个整数,表示一个操作,具体如下:

操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k

操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和

 

输出格式:

 

输出包含若干行整数,即为所有操作2的结果。

#include<cctype>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
inline long long read(){
	long long a=0; int f=0; char c=getchar();
	while(c<'0'||c>'9') { f|=c=='-'; c=getchar(); }
	while(c>='0'&&c<='9') { a=(a<<3)+(a<<1)+(c^48);	c=getchar(); }
	return f? -a:a;
}
int n,m,x,y,z,a,f[100001];
long long ans;
struct qwq{ int l,r,lz; long long w; } tree[400004];
void build(int k,int ll,int rr){//建树 
	tree[k].l=ll,tree[k].r=rr; 
	if(tree[k].l==tree[k].r){
		tree[k].w=f[ll];
		return ;
	}
	int m=((ll+rr)>>1),lls=(k<<1),rrs=((k<<1)|1);
	build(lls,ll,m),build(rrs,m+1,rr);
	tree[k].w=tree[lls].w+tree[rrs].w;
}
void down(int k){//下传lazy标记 
	int lls=(k<<1),rrs=((k<<1)|1);
	tree[lls].lz+=tree[k].lz,tree[rrs].lz+=tree[k].lz;
	tree[lls].w+=tree[k].lz*(tree[lls].r-tree[lls].l+1);
	tree[rrs].w+=tree[k].lz*(tree[rrs].r-tree[rrs].l+1);
	tree[k].lz=0;
}
void cinterval(int k){//区间修改 
	if(tree[k].l>=x&&tree[k].r<=y){
		tree[k].w+=(tree[k].r-tree[k].l+1)*z;
		tree[k].lz+=z;
		return ;
	}
	if(tree[k].lz) down(k);
	int m=((tree[k].l+tree[k].r)>>1),lls=(k<<1),rrs=((k<<1)|1);
	if(x<=m) cinterval(lls);
	if(y>m) cinterval(rrs);
	tree[k].w=tree[lls].w+tree[rrs].w;
}
void ainterval(int k){//区间查询 
	if(tree[k].l>=x&&tree[k].r<=y){
		ans+=tree[k].w;
		return ;
	}
	if(tree[k].lz) down(k);
	int m=((tree[k].l+tree[k].r)>>1);
	if(x<=m) ainterval((k<<1));
	if(y>m) ainterval((k<<1)+1);
}
void cpoint(int k){//单点修改 
	if(tree[k].l==tree[k].r){
		tree[k].w+=z;
		return ;
	}
	if(tree[k].lz) down(k);
	int m=((tree[k].l+tree[k].r)>>1),lls=(k<<1),rrs=((k<<1)|1);
	if(x<=m) cpoint(lls);
	else cpoint(rrs);
	tree[k].w=tree[lls].w+tree[rrs].w;
} 
void apoint(int k){//单点查询 
	if(tree[k].l==tree[k].r){
		ans=tree[k].w;
		return ;
	}
	if(tree[k].lz) down(k);
	int m=((tree[k].l+tree[k].r)>>1);
	if(x<=m) apoint((k<<1));
	else apoint((k<<1)+1);
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;++i) f[i]=read();
	build(1,1,n);
	for(int i=1;i<=m;++i){
		a=read(),x=read(),y=read();
		if(a==1) z=read(),cinterval(1);
		else{
			ans=0,ainterval(1);
			printf("%lld\n",ans);
		}
	}
	return 0;
}
//HMR同學的據說是最好看的版本qwq
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#define ll long long
#define gc getchar
#define maxn 100005
using namespace std;

inline ll read(){
	ll a=0;int f=0;char p=gc();
	while(!isdigit(p)){f|=p=='-';p=gc();}
	while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
	return f?-a:a;
}int n,m;
ll a[maxn];

struct ahaha{
	ll v,lz;
}t[maxn<<2];
#define lc p<<1
#define rc p<<1|1
inline void pushup(int p){
	t[p].v=t[lc].v+t[rc].v;
}
inline void pushdown(int p,int l,int r){
	int m=l+r>>1;ll &lz=t[p].lz;
	t[lc].v+=lz*(m-l+1);t[lc].lz+=lz;
	t[rc].v+=lz*(r-m);t[rc].lz+=lz;
	lz=0;
}
void build(int p,int l,int r){
	if(l==r){t[p].v=a[l];return;}
	int m=l+r>>1;
	build(lc,l,m);build(rc,m+1,r);
	pushup(p);
}
void update(int p,int l,int r,int L,int R,ll z){
	if(l>R||r<L)return;
	if(L<=l&&r<=R){t[p].v+=z*(r-l+1);t[p].lz+=z;return;}
	int m=l+r>>1;if(t[p].lz)pushdown(p,l,r);
	update(lc,l,m,L,R,z);update(rc,m+1,r,L,R,z);
	pushup(p);
}
ll query(int p,int l,int r,int L,int R){
	if(l>R||r<L)return 0;
	if(L<=l&&r<=R)return t[p].v;
	int m=l+r>>1;if(t[p].lz)pushdown(p,l,r);
	return query(lc,l,m,L,R)+query(rc,m+1,r,L,R);
}

inline void solve_1(){
	int x=read(),y=read();ll z=read();
	update(1,1,n,x,y,z);
}
inline void solve_2(){
	int x=read(),y=read();
	printf("%lld\n",query(1,1,n,x,y));
}

int main(){
	n=read();m=read();
	for(int i=1;i<=n;++i)
		a[i]=read();
	build(1,1,n);
	while(m--){
		int zz=read();
		switch(zz){
			case 1:solve_1();break;
			case 2:solve_2();break;
		}
	}
	return 0;
}

  

Luogu【P3373】 【模板】線段樹2

 

题目描述

如题,已知一个数列,你需要进行下面三种操作:

1.将某区间每一个数乘上x

2.将某区间每一个数加上x

3.求出某区间每一个数的和

输入输出格式

输入格式:

 

第一行包含三个整数N、M、P,分别表示该数列数字的个数、操作的总个数和模数。

第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

接下来M行每行包含3或4个整数,表示一个操作,具体如下:

操作1: 格式:1 x y k 含义:将区间[x,y]内每个数乘上k

操作2: 格式:2 x y k 含义:将区间[x,y]内每个数加上k

操作3: 格式:3 x y 含义:输出区间[x,y]内每个数的和对P取模所得的结果

 

输出格式:

 

输出包含若干行整数,即为所有操作3的结果。

#include<cctype>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
inline long long read(){
    long long a=0; int f=0; char c=getchar();
    while(c<'0'||c>'9') { f|=c=='-'; c=getchar(); }
    while(c>='0'&&c<='9') { a=(a<<3)+(a<<1)+(c^48);	c=getchar(); }
    return f? -a:a;
}
int n,m,a,b,c,d;
long long ans,mod;
long long f[100001];
struct Tree{ int l,r; long long w,lz1,lz2; } tree[400004];
inline void build(int k,int ll,int rr){
    tree[k].l=ll,tree[k].r=rr,tree[k].lz1=1;
    if(ll==rr){
        tree[k].w=f[ll];
        return ;
    }
    int mid=((ll+rr)>>1);
    build((k<<1),ll,mid),build((k<<1)+1,mid+1,rr);
    tree[k].w=tree[(k<<1)].w+tree[(k<<1)+1].w;
}
inline void down(int k){
    int ls=(k<<1),rs=((k<<1)|1);
    tree[ls].w=(tree[ls].w*tree[k].lz1+(tree[ls].r-tree[ls].l+1)*tree[k].lz2)%mod;
    tree[rs].w=(tree[rs].w*tree[k].lz1+(tree[rs].r-tree[rs].l+1)*tree[k].lz2)%mod;
    tree[ls].lz1=(tree[ls].lz1*tree[k].lz1)%mod;
    tree[rs].lz1=(tree[rs].lz1*tree[k].lz1)%mod;
    tree[ls].lz2=(tree[ls].lz2*tree[k].lz1+tree[k].lz2)%mod;
    tree[rs].lz2=(tree[rs].lz2*tree[k].lz1+tree[k].lz2)%mod;
    tree[k].lz1=1,tree[k].lz2=0;
}
inline void ch1(int k){
    if(tree[k].l>=b&&tree[k].r<=c){
        tree[k].w=(tree[k].w*d)%mod;
        tree[k].lz1=(tree[k].lz1*d)%mod,tree[k].lz2=(tree[k].lz2*d)%mod;
        return ;
    }
    if(tree[k].lz1!=1||tree[k].lz2) down(k);
    int mid=((tree[k].l+tree[k].r)>>1),lls=(k<<1),rrs=(lls|1);
    if(b<=mid) ch1(lls);
    if(c>mid) ch1(rrs);
    tree[k].w=(tree[lls].w+tree[rrs].w)%mod;
}
inline void ch2(int k){
    if(tree[k].l>=b&&tree[k].r<=c){
        tree[k].w=(tree[k].w+(tree[k].r-tree[k].l+1)*d)%mod;
        tree[k].lz2=(tree[k].lz2+d)%mod;
        return ;
    }
    if(tree[k].lz1!=1||tree[k].lz2) down(k);
    int mid=((tree[k].l+tree[k].r)>>1),lls=(k<<1),rrs=(lls|1);
    if(b<=mid) ch2(lls);
    if(c>mid) ch2(rrs);
    tree[k].w=(tree[lls].w+tree[rrs].w)%mod;	
}
inline void ask(int k){
    if(tree[k].l>=b&&tree[k].r<=c){
        ans=(ans+tree[k].w)%mod;
        return ;
    }
    if(tree[k].lz1!=1||tree[k].lz2) down(k);
    int mid=((tree[k].l+tree[k].r)>>1),lls=(k<<1),rrs=(lls|1);
    if(b<=mid) ask(lls);
    if(c>mid) ask(rrs);
}
int main(){
    n=read(),m=read(),mod=read();
    for(int i=1;i<=n;++i) f[i]=read()%mod;
    build(1,1,n);
    while(m--){
        a=read(),b=read(),c=read();
        if(a==1) d=read(),ch1(1);
        else if(a==2) d=read(),ch2(1);
             else ans=0,ask(1),printf("%lld\n",ans);
    }
    return 0;
}

  

//HMR神仙的美麗的代碼!
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#define ll long long
#define gc getchar
#define maxn 100005
using namespace std;

inline ll read(){
	ll a=0;int f=0;char p=gc();
	while(!isdigit(p)){f|=p=='-';p=gc();}
	while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
	return f?-a:a;
}int n,m;
ll mo,a[maxn];

struct ahaha{
	ll v,lz,mul;
	ahaha(){
		mul=1;
	}
}t[maxn<<2];
#define lc p<<1
#define rc p<<1|1
inline void pushup(int p){
	t[p].v=t[lc].v+t[rc].v;
}
inline void pushdown(int p,int l,int r){
	int m=l+r>>1;ll &lz=t[p].lz,&mul=t[p].mul;
	if(mul!=1){
		t[lc].v=t[lc].v*mul%mo;t[rc].v=t[rc].v*mul%mo;
		t[lc].mul=t[lc].mul*mul%mo;t[rc].mul=t[rc].mul*mul%mo;
		t[lc].lz=t[lc].lz*mul%mo;t[rc].lz=t[rc].lz*mul%mo;
		mul=1;
	}
	if(lz){
		t[lc].v=(t[lc].v+lz*(m-l+1))%mo;t[lc].lz=(t[lc].lz+lz)%mo;
		t[rc].v=(t[rc].v+lz*(r-m))%mo;t[rc].lz=(t[rc].lz+lz)%mo;
		lz=0;
	}
}
void build(int p,int l,int r){
	if(l==r){t[p].v=a[l];return;}
	int m=l+r>>1;
	build(lc,l,m);build(rc,m+1,r);
	pushup(p);
}
void update1(int p,int l,int r,int L,int R,ll z){
	if(l>R||r<L)return;
	if(L<=l&&r<=R){
		t[p].v=t[p].v*z%mo;
		t[p].mul=t[p].mul*z%mo;
		t[p].lz=t[p].lz*z%mo;
		return;
	}
	int m=l+r>>1;if(t[p].mul!=1||t[p].lz)pushdown(p,l,r);
	update1(lc,l,m,L,R,z);update1(rc,m+1,r,L,R,z);
	pushup(p);
}
void update2(int p,int l,int r,int L,int R,ll z){
	if(l>R||r<L)return;
	if(L<=l&&r<=R){
		t[p].v=(t[p].v+(r-l+1)*z%mo)%mo;
		t[p].lz=(t[p].lz+z)%mo;
		return;
	}
	int m=l+r>>1;if(t[p].mul!=1||t[p].lz)pushdown(p,l,r);
	update2(lc,l,m,L,R,z);update2(rc,m+1,r,L,R,z);
	pushup(p);
}
ll query(int p,int l,int r,int L,int R){
	if(l>R||r<L)return 0;
	if(L<=l&&r<=R)return t[p].v;
	int m=l+r>>1;if(t[p].mul!=1||t[p].lz)pushdown(p,l,r);
	return (query(lc,l,m,L,R)+query(rc,m+1,r,L,R))%mo;
}

inline void solve_1(){
	int x=read(),y=read();ll z=read()%mo;
	update1(1,1,n,x,y,z);
}
inline void solve_2(){
	int x=read(),y=read();ll z=read()%mo;
	update2(1,1,n,x,y,z);
}
inline void solve_3(){
	int x=read(),y=read();
	printf("%lld\n",query(1,1,n,x,y));
}

int main(){
	n=read();m=read();mo=read();
	for(int i=1;i<=n;++i)
		a[i]=read();
	build(1,1,n);
	while(m--){
		int zz=read();
		switch(zz){
			case 1:solve_1();break;
			case 2:solve_2();break;
			case 3:solve_3();break;
		}
	}
	return 0;
}

  

  

 

Q:如果當前訪問的區間不是要修改/查詢的區間,為什麼要下放標記呢?

A:因為要修改/查詢的區間可能是當前區間的子區間,所以如果不下放標記,該區間的子區間的Lazy標記就可能是錯的。  

posted @ 2018-11-01 08:10  Salfi_Holmes  阅读(120)  评论(6编辑  收藏  举报