1. 程式人生 > >「模板」線段樹靜態開點(單點+區間修改)、動態開點

「模板」線段樹靜態開點(單點+區間修改)、動態開點

條件判斷 else detail algo query std 判斷 hup cout

相關講解資料:

樹狀數組:https://blog.csdn.net/qq_34374664/article/details/52787481 (線段樹預備)

線段樹講解:     初學版:https://blog.csdn.net/zearot/article/details/52280189     進階完整版:https://www.cnblogs.com/AC-King/p/7789013.html 代碼:

完整註釋模板一張,參(chao)考(xi)樓上的博客

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include
<cstring> #include<cmath> #include<queue> #include<algorithm> using namespace std; typedef long long ll; #define mid (l+r)/2 #define lson (pos<<1) #define rson ((pos<<1)|1) #define maxn 100007 //元素個數 ll n,m; ll root=1; ll arr[maxn]; ll Lazy[maxn<<2
];//區間增加的lazy標記 /*其目的是: 為防止修改區間總結點對每個子節點都要進行修改,導致復雜度爆炸 暫時記錄一下這個區間總結點的所有子樹都“待修改” 如果用到下面的子節點就修改,下推lazy標誌,用不到就不管 以此來減少復雜度 */ ll sum[maxn<<2];//線段樹求和最多分成4個子區間 void PushUp(long long pos)//暫時寫成求和函數,可以自由變換 { sum[pos]=sum[lson]+sum[rson];
//用數組表示二叉樹:假設某個節點的編號為v,那麽它的左子節點編號為2*v,右子節點編號為2*v+1,規定根節點為1 //通常2*v寫成v<<1 , 2*v+1寫成v<<1|1; } void PushDown(long long pos,long long l,long long r)//區間查詢用 { //l,r為左子樹,右子樹的數字區間 if(Lazy[pos]) { //修改子節點的增加數 Lazy[lson]+=Lazy[pos]; Lazy[rson]+=Lazy[pos]; //修改子節點區間的sum sum[lson]+=Lazy[pos]*(mid-l+1); sum[rson]+=Lazy[pos]*(r-(mid+1)+1); //清除本節點標記 Lazy[pos]=0; } } void Build(long long l,long long r,long long pos)//[l,r]表示當前節點區間,pos表示當前節點的實際存儲位置 { if(l==r)//如果到達兒子節點,存儲並返回 { sum[pos]=arr[l]; return; } Build(l,mid,pos<<1); Build(mid+1,r,pos<<1|1); PushUp(pos); } void UpPoint(long long pos,long long l,long long r,long long L,long long C)//對單點修改 { //L表示要修改的點編號,[l,r]表示當前區間,pos是當前節點編號; if(l==r)//到達兒子節點之後就修改 { sum[pos]+=C; return; } //根據條件判斷往左子樹調用還是往右 if(L<=mid) UpPoint(lson,l,mid,L,C); else UpPoint(rson,mid+1,r,L,C); PushUp(pos);//子節點更新之後本節點也需要更新; } void UpZone(long long pos,long long l,long long r,long long L,long long R,long long C)//對整個區間進行修改 { //L,R表示操作區間 , l,r表示當前節點區間 , pos表示當前節點編號 if(L<=l && R>=r)//節點區間在操作區間之內,直接返回 { sum[pos]+=C*(r-l+1);//這個點需要加上區間長度*C Lazy[pos]+=C;//用Lazy標記,表示本區間的Sum正確,子區間的Sum仍需要根據Add調整 return; } PushDown(pos,l,r);//下推標記 if(L<=mid) UpZone(lson,l,mid,L,R,C); if(R>mid) UpZone(rson,mid+1,r,L,R,C); PushUp(pos); } ll Query(long long l,long long r,long long L,long long R,long long pos) { //L,R表示操作區間 , l,r表示當前節點區間 , pos表示當前節點編號 if(L<=l && R>=r)//節點區間在操作區間之內,直接返回 { return sum[pos]; } PushDown(pos,l,r);//下推標記,否則sum可能不正確 //統計答案 long long ans=0; if(L<=mid) ans+=Query(l,mid,L,R,lson); if(R>mid) ans+=Query(mid+1,r,L,R,rson); PushUp(pos); return ans; } int main() { cin>>n>>m; for(int i=1;i<=n;i++) { long long tmp; cin>>tmp; UpZone(root,1,n,i,i,tmp); } for(int j=1;j<=m;j++) { long long a,b,c,d; cin>>a; if(a==1) { cin>>b>>c>>d; UpZone(root,1,n,b,c,d); } else { cin>>b>>c; cout<<Query(1,n,b,c,root)<<endl; } } return 0; }

下面是動態開點的模板:

1. 不能define lson,rson,也不能用pos<<1和pos<<1|1,否則就失去了“動態開點”的意義
2. Get_Son和UpZone要&引用
3. 盡量開long long,也好調

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;

//!!!!!!!!!
//Get_Son和UpZone要&引用
//不能define  lson,rson,也不能用pos<<1和pos<<1|1
//!!!!!!!!!

#define mid  (l+r)/2
#define maxn 1000007 //元素個數

ll n,m;
ll root=1,cnt=1;
ll lson[maxn],rson[maxn];
ll Lazy[maxn<<2];//區間增加的lazy標記
/*其目的是:
            為防止修改區間總結點對每個子節點都要進行修改,導致復雜度爆炸
            暫時記錄一下這個區間總結點的所有子樹都“待修改”
            如果用到下面的子節點就修改,下推lazy標誌,用不到就不管
            以此來減少復雜度
*/
ll sum[maxn<<2];//線段樹求和最多分成4個子區間

ll Get_Son(long long &pos)
{
    if(pos==0) pos=++cnt;
    return pos;
}

void PushUp(long long pos)
{
    sum[pos]=sum[lson[pos]]+sum[rson[pos]];
    //用數組表示二叉樹:假設某個節點的編號為v,那麽它的左子節點編號為2*v,右子節點編號為2*v+1,規定根節點為1
    //通常2*v寫成v<<1 , 2*v+1寫成v<<1|1;
}

void PushDown(long long pos,long long l,long long r)//區間查詢用
{
    //l,r為左子樹,右子樹的數字區間

    // if(Lazy[pos]==0) return;
    // if(r-l<=1) return;
    // if(pos<<1!=0)
    // {
    //     pos<<1=++cnt;
    //     sum[pos<<1]+=(mid-l+1)*Lazy[pos];
    //     Lazy[pos<<1]+=Lazy[pos];
    // }
    // if(rson[pos]!=0)
    // {
    //     rson[pos]=++cnt;
    //     sum[rson[pos]]+=(r-mid+1)*Lazy[pos];
    //     Lazy[rson[pos]]+=Lazy[pos];
    // }
    sum[Get_Son(lson[pos])]+=(mid-l+1)*Lazy[pos];
    sum[Get_Son(rson[pos])]+=(r-mid)*Lazy[pos];
    Lazy[lson[pos]]+=Lazy[pos];
    Lazy[rson[pos]]+=Lazy[pos];
    Lazy[pos]=0;
}

void UpZone(long long &pos,long long l,long long r,long long L,long long R,long long C)
{
    //L,R表示操作區間 , l,r表示當前節點區間 , pos表示當前節點編號
    if(pos==0) pos=++cnt;
    if(Lazy[pos]!=0) PushDown(pos,l,r);//下推標記
    
    if(L<=l && R>=r)//節點區間在操作區間之內,直接返回
    {
        sum[pos]+=(r-l+1)*C;//這個點需要加上區間長度*C
        Lazy[pos]+=C;//用Lazy標記,表示本區間的Sum正確,子區間的Sum仍需要根據Lazy調整
        return;
    }

    if(L<=mid) UpZone(lson[pos],l,mid,L,R,C);
    if(R>mid) UpZone(rson[pos],mid+1,r,L,R,C);
    PushUp(pos);
}

ll Query(long long pos,long long l,long long r,long long L,long long R)
{
    //L,R表示操作區間 , l,r表示當前節點區間 , pos表示當前節點編號
    if(pos==0) return 0;
    if(Lazy[pos]) PushDown(pos,l,r);//下推標記,否則sum可能不正確

    if(L<=l && R>=r)//節點區間在操作區間之內,直接返回
    {
        return sum[pos];
    }
    
    //統計答案
    long long ans=0;
    if(L<=mid) ans+=Query(lson[pos],l,mid,L,R);
    if(R>mid) ans+=Query(rson[pos],mid+1,r,L,R);
    PushUp(pos);
    return ans;
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int tmp;
        cin>>tmp;
        UpZone(root,1,n,i,i,tmp);
    }
    for(int j=1;j<=m;j++)
    {
        int a,b,c,d;
        cin>>a;
        if(a==1)
        {
            cin>>b>>c>>d;
            UpZone(root,1,n,b,c,d);
        }
        else
        {
            cin>>b>>c;
            cout<<Query(root,1,n,b,c)<<endl;
        }
    }
    return 0;
}

「模板」線段樹靜態開點(單點+區間修改)、動態開點