• 線段樹


    參考資料: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
    ",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
    ",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
    ",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
    ",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標記就可能是錯的。  

  • 相关阅读:
    Codeforces 468 B Two Sets
    POJ 3080 Blue Jeans
    Scan法求凸包
    线段树 区间更新 区间求和 板子
    拼图设计 课程作业三
    通讯录c#实现 课程作业二
    贷款计算器C#实现 课程作业一
    标准计算器C#实现 课程作业一
    ccf 行车路线
    hdu 4902 Nice boat
  • 原文地址:https://www.cnblogs.com/azureholmes/p/9880868.html
Copyright © 2020-2023  润新知