1. 程式人生 > >【線段樹】【P3372】模板-線段樹

【線段樹】【P3372】模板-線段樹

1.5 情況 一行 標記 ask lan build put 同時

百度百科

Definition&Solution

  線段樹是一種log級別的樹形結構,可以處理區間修改以及區間查詢問題。期望情況下,復雜度為O(nlogn)。

   核心思想見百度百科,線段樹即將每個線段分成左右兩個線段做左右子樹。一個線段沒有子樹,當且僅當線段表示的區間為[a,a]。

   由於編號為k的節點的子節點為2k以及2k+1,線段樹可以快速的遞歸左右葉節點。

   lazy標記:當進行區間修改的時候,如果一個區間整體全部被包含於要修改的區間,則可以將該區間的值修改後,將lazy標記打在區間上,不再遞歸左右區間。

   例如,要修改[15,30]區間整體+2,當前區間為[16,24],被包含於要修改的區間。記代表區間[16,24]的節點編號為k,則tree[k]+=2*(24-16+1),同時lazy[k]+=2。

   在下次修改或查詢到k節點時,進行lazy的下放,即如下代碼

inline void Free(cl l,cl r,cl p) {
    ll m=(l+r)>>1,dp=p<<1;
    tree[dp]+=(m-l+1)*lazy[p];tree[dp+1]+=(r-m)*lazy[p];
    lazy[dp]+=lazy[p];lazy[dp+1]+=lazy[p];
    lazy[p]=0;
}

   註意:被打上lazy標記的區間實際上已經修改完區間和,每次free修改的是子區間

Example

傳送門

Description

已知一個數列,你需要進行下面兩種操作:

1.將某區間每一個數加上x

2.求出某區間每一個數的和

Input

第一行包含兩個整數N、M,分別表示該數列數字的個數和操作的總個數。

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

接下來M行每行包含3或4個整數,表示一個操作,具體如下:

操作1: 格式:1 x y k 含義:將區間[x,y]內每個數加上k

操作2: 格式:2 x y 含義:輸出區間[x,y]內每個數的和

Output

輸出包含若幹行整數,即為所有操作2的結果。

Sample Input

5 5
1 5 4 2 3
2 2 4
1 2 3 2 2 3 4 1 1 5 1 2 1 4

Sample Output

11
8
20

Hint

時空限制:1000ms,128M

數據規模:

對於30%的數據:N<=8,M<=10

對於70%的數據:N<=1000,M<=10000

對於100%的數據:N<=100000,M<=100000

Solution

模板題。有一些需要註意的地方會在summary寫明

Code

#include<cstdio>
#define maxn 100010
#define maxt 400010
#define ll long long int
#define cl const long long int

inline void qr(long long &x) {
    char ch=getchar();long long f=1;
    while(ch>9||ch<0)    {
        if(ch==-)    f=-1;
        ch=getchar();
    }
    while(ch>=0&&ch<=9)    x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x*=f;
    return;
}

inline long long max(const long long &a,const long long &b) {if(a>b) return a;else return b;}
inline long long min(const long long &a,const long long &b) {if(a<b) return a;else return b;}
inline long long abs(const long long &x) {if(x>0) return x;else return -x;}

inline void swap(long long &a,long long &b) {
    long long c=a;a=b;b=c;return;
}

ll n,m,MU[maxn],sign,a,b,c;

ll tree[maxt],lazy[maxt];

void build(const ll l,const ll r,const ll p) {
    if(l>r)    return;
    if(l==r) {tree[p]=MU[l];return;}
    ll m=(l+r)>>1,dp=p<<1;
    build(l,m,dp);build(m+1,r,dp+1);
    tree[p]=tree[dp]+tree[dp+1];
}

inline void Free(cl l,cl r,cl p) {
    ll m=(l+r)>>1,dp=p<<1;
    tree[dp]+=(m-l+1)*lazy[p];tree[dp+1]+=(r-m)*lazy[p];
    lazy[dp]+=lazy[p];lazy[dp+1]+=lazy[p];
    lazy[p]=0;
}

inline void wohenlan(cl l,cl r,cl p,cl v) {tree[p]+=(r-l+1)*v;lazy[p]+=v;}

void add(cl l,cl r,cl p,cl aiml,cl aimr,cl v) {
    if(l>r) return;
    if(l>aimr||r<aiml) {return;}
    if(l>=aiml&&r<=aimr) {wohenlan(l,r,p,v);return;}
    Free(l,r,p);
    ll m=(l+r)>>1,dp=p<<1;
    add(l,m,dp,aiml,aimr,v);add(m+1,r,dp+1,aiml,aimr,v);
    tree[p]=tree[dp]+tree[dp+1];
}

ll ask(cl l,cl r,cl p,cl aiml,cl aimr) {
    if(l>r) return 0;
    if(l>aimr||r<aiml) {return 0;}
    if(l>=aiml&&r<=aimr) {return tree[p];}
    Free(l,r,p);
    ll m=(l+r)>>1,dp=p<<1;
    return ask(l,m,dp,aiml,aimr)+ask(m+1,r,dp+1,aiml,aimr);
}

int main() {
    qr(n);qr(m);
    for(int i=1;i<=n;++i) qr(MU[i]);
    build(1ll,n,1ll);
    while(m--) {
        sign=a=b=0;qr(sign);qr(a);qr(b);
        if(sign==1) {
            c=0;qr(c);
            add(1,n,1,a,b,c);
        }
        else printf("%lld\n",ask(1, n, 1, a, b));
    }
    return 0;
}

Summary

  1、線段樹大小要開4*n。理論上線段樹會有2*n個子節點,但是試試這棵線段樹:1 2 3 4 5

      如圖所示:技術分享圖片

         可以看到,節點數確實是6*2-1=11個,但是由於我們每個節點編號都嚴格按照母節點*2(+1)進行編號,所以我們的編號開到了2*n之外。開4*n是比較保險的。

    2、註意對lazy標記的free操作要在確定區間可以再分以後進行。即先寫

if(l>=aiml&&r<=aimr) {wohenlan(l,r,p,v);return;}

      或

if(l>=aiml&&r<=aimr) {return tree[p];}

      後,如果沒有return,則證明區間一定是可再分的,即還沒有遞歸到葉節點,這時才可以進行free操作。否則的話考慮在葉節點的編號可能大於2*n,我們在葉節點free了一下,標記被下放到了4*n以外……

      然後你就炸了。

【線段樹】【P3372】模板-線段樹