1. 程式人生 > >線段樹(區間操作+lazy) 很好的模板以及心得體會

線段樹(區間操作+lazy) 很好的模板以及心得體會

這裡只討論區間處理+lazy

acm比賽考察不會出裸的模板,所以必須深刻理解線段樹原理,各函式的目的。

#include <string.h>  
#include <algorithm>  
#include <stdio.h>  
#include <math.h>  
#include <queue>  
#define MAXN 100010  
#define inf 0x3f3f3f3f  
  
using namespace std;  
  
struct node{  
    int l,r;//區間[l,r]  
    int add;//區間的延時標記  
    int sum;//區間和  
    int mx; //區間最大值  
    int mn; //區間最小值  
}tree[MAXN<<2];//一定要開到4倍多的空間  
  
void pushup(int index){  
    tree[index].sum = tree[index<<1].sum+tree[index<<1|1].sum;  
    tree[index].mx = max(tree[index<<1].mx,tree[index<<1|1].mx);  
    tree[index].mn = min(tree[index<<1].mn,tree[index<<1|1].mn);  
}  
void pushdown(int index){  
    //說明該區間之前更新過  
    //要想更新該區間下面的子區間,就要把上次更新該區間的值向下更新  
    if(tree[index].add){  
        //替換原來的值  
        /* 
        tree[index<<1].sum = (tree[index<<1].r-tree[index<<1].l+1)*tree[index].add; 
        tree[index<<1|1].sum = (tree[index<<1|1].r-tree[index<<1|1].l+1)*tree[index].add; 
        tree[index<<1].mx = tree[index].add; 
        tree[index<<1|1].mx = tree[index].add; 
        tree[index<<1].mn = tree[index].add; 
        tree[index<<1|1].mn = tree[index].add; 
        tree[index<<1].add = tree[index].add; 
        tree[index<<1|1].add = tree[index].add; 
        tree[index].add = 0;*/  
        //在原來的值的基礎上加上val  
          
        tree[index<<1].sum += (tree[index<<1].r-tree[index<<1].l+1)*tree[index].add;  
        tree[index<<1|1].sum +=(tree[index<<1|1].r-tree[index<<1|1].l+1)*tree[index].add;  
        tree[index<<1].mx += tree[index].add;  
        tree[index<<1|1].mx += tree[index].add;  
        tree[index<<1].mn += tree[index].add;  
        tree[index<<1|1].mn += tree[index].add;  
        tree[index<<1].add += tree[index].add;  
        tree[index<<1|1].add += tree[index].add;  
        tree[index].add = 0;  
  
    }  
}  
void build(int l,int r,int index){  
    tree[index].l = l;  
    tree[index].r = r;  
    tree[index].add = 0;//剛開始一定要清0  
    if(l == r){  
        scanf("%d",&tree[index].sum);  
        tree[index].mn = tree[index].mx = tree[index].sum;  
        return ;  
    }  
    int mid = (l+r)>>1;  
    build(l,mid,index<<1);  
    build(mid+1,r,index<<1|1);  
    pushup(index);  
}  
void updata(int l,int r,int index,int val){  
    if(l <= tree[index].l && r >= tree[index].r){  
        /*把原來的值替換成val,因為該區間有tree[index].r-tree[index].l+1 
        個數,所以區間和 以及 最值為: 
        */  
        /*tree[index].sum = (tree[index].r-tree[index].l+1)*val; 
        tree[index].mn = val; 
        tree[index].mx = val; 
        tree[index].add = val;//延時標記*/  
        //在原來的值的基礎上加上val,因為該區間有tree[index].r-tree[index].l+1  
        //個數,所以區間和 以及 最值為:  
        tree[index].sum += (tree[index].r-tree[index].l+1)*val;  
        tree[index].mn += val;  
        tree[index].mx += val;  
        tree[index].add += val;//延時標記  
  
        return ;  
    }  
    pushdown(index);  
    int mid = (tree[index].l+tree[index].r)>>1;  
    if(l <= mid){  
        updata(l,r,index<<1,val);  
    }  
    if(r > mid){  
        updata(l,r,index<<1|1,val);  
    }  
    pushup(index);  
}  
int query(int l,int r,int index){  
    if(l <= tree[index].l && r >= tree[index].r){  
        //return tree[index].sum;  
        return tree[index].mx;  
        //return tree[index].mn;  
    }  
    pushdown(index);  
    int mid = (tree[index].l+tree[index].r)>>1;  
    int ans = 0;  
    int Max = 0;  
    int Min = inf;  
    if(l <= mid){  
        ans += query(l,r,index<<1);  
        Max = max(query(l,r,index<<1),Max);  
        Min = min(query(l,r,index<<1),Min);  
    }  
    if(r > mid){  
        ans += query(l,r,index<<1|1);  
        Max = max(query(l,r,index<<1|1),Max);  
        Min = min(query(l,r,index<<1|1),Min);  
    }  
    //return ans;  
    return Max;  
    //return Min;  
}  
int main()  
{  
    int n,m,q,x,y,z;  
    while(~scanf("%d%d",&n,&m)){  
        build(1,n,1);  
        while(m--){  
            scanf("%d",&q);  
            if(q == 1){  
                cout<<"查詢:(x,y)"<<endl;  
                scanf("%d %d",&x,&y);  
                cout<<query(x,y,1)<<endl;  
            }  
            else{  
                cout<<"更新(x,y)為z:"<<endl;  
                scanf("%d %d %d",&x,&y,&z);  
                updata(x,y,1,z);  
                for(int i = 1; i <= n; ++i){  
                    printf("a[%d] = %d\n",i,query(i,i,1));  
                }  
            }  
        }  
    }  
    return 0;  
}

拿到問題,簡單數學化題目要求 , 比如說這題

題意: 

兩種操作 1)訂房間 給定長度x ,求連續長度>=x 的區間起始位置, 儘量靠左 , 查詢到結果後把該段長度標記為不可用

2) 退房間 給定xy,把xy區間標記為可用

思路:

操作1 等價於兩部, 1 查詢到可用區間的起始位置, 2 更新該段區間為不可用

操作2 等價於更新該段區間為可用

分析 :

資料結構

我們要知道線段樹資料結構需要哪些內容, l,r,lazy (必備) 、v(區間[l,r]內連續最大區間,用於查詢時判斷是否進入這個區間)、llen,rlen(區間[l,r]的字首, 字尾長度。 舉例分析 ,假設n為6,所需x為4 . 則一開始進入[1,6]。分成兩個區間[1,3][4,6[ 。這個時候左子樹的字尾 + 右子樹的字首可以符合條件,我們就要把答左子樹的字尾起點就是我們要的案)

struct node
{
    int l,r,v,lazy,llen,rlen;
    void changeLen()
    {
        llen = rlen = v = lazy*(r-l+1);
    }
}node[N<<2];    //  線段樹的空間大概是陣列空間的4倍;

changelen()的作用,lazy標記了區間[l,r]的可用性(整體),那麼[l,r]內的v,llen,rlen 就等於 區間長度*lazy。

通過lazy的改變,修改最長連續區間、左後綴、右字首的值

build 建樹

定位到葉子節點後,標記lazy的1(通過changelen函式,修改v,llen, rlen 為 1) 。

儲存資訊:node[numb].l = l , node[numb].r = r(必須的,線段樹的基本)

void build(int l,int r,int numb)    //  線段樹的建立;
{
    node[numb].lazy=1;
    node[numb].l = l;
    node[numb].r = r;
    node[numb].changeLen();
    if(l==r) return;
    int mid=(l+r)>>1;
    build(l,mid,numb<<1);
    build(mid+1,r,numb<<1|1);
}

change 更新操作

更新操作模板的架構基本是這樣的 


if (區間重合,即定位到目標區間)
{
    lazy標記 // 節約時間,不向下走
    return;
}

// 沒有定位到目標區間,那麼向下一層走

if(node[numb].lazy != -1) // 向下走之前,倘若節點上有lazy標誌,那麼先更新子樹資訊,才能向下走
    pushDown(numb)
mid = (node[numb].l + node[numb].r) / 2;
if(l>mid) // 目標區間 在本節點的右子樹上
    更新右邊
else if(r<=mid)
    更新左邊
else{ // 左右各佔一部分,分別更新
    更新左
    更新右
}
pushUp(numb); // 更新完左右子樹,那麼本節點的資訊也要修改

本題中,我們要明白更新時,那些引數是需要改變的,

1)我們定位到目標區間,更新其lazy,馬上能得到該區間上的 v=llen = rlen因此changelen()

2)PushDown ,向下傳遞,那麼子節點上的 lazy 就該被更新為 父節點上的lazy (這裡注意,本題的lazy取值只有1可用,0不可用,-1沒有lazy , 其他題有的lazy暫存的是操作次數那麼應該累加,比如對區間做加法,那麼向下傳遞應該是node[rt<<1].lazy+=node[rt].lazy)。並且同時更新子節點上的v,llen,rlen

3)PushUp , 用左右節點的資訊更新父節點。逐個分析父節點的各個變數 ,

 l r lazy :跟子樹無關 。

v:   修改為 左節點的v 和 右節點的v 以及 左節點字尾 + 右節點字首 三者的最大值

llen:  左節點的字首, 當左節點區間都是可用時,那麼還需加上右節點的字首 (兩個區間能連起來)

rlen: 右節點的字尾, 當右節點區間都是可用時,還需加上左節點的字尾

void PushDown(int numb)             //  向下往左右兒子方向更新資料;
{
    // 為了父節點的lazy 對左右子樹的影響,要思考lazy會影響左右子樹的什麼
    // 本題,lazy肯定要向下傳遞,子樹再根據lazy ,修改自身變數
    //(標記為0 相當於該區間完全被佔用,也就是llen rlen v = 0 )

    // 最後 把父節點的 lazy 標記取消 變成-1
    node[numb<<1].lazy=node[numb].lazy;
    node[numb<<1|1].lazy=node[numb].lazy;
    node[numb<<1].changeLen();
    node[numb<<1|1].changeLen();

    node[numb].lazy=-1;              //  更新完了要清零;
}
void PushUp(int numb)               
{
    // 假設父節點是A ,左右子樹分別是BC
    // pushUp時 關心BC,對A的影響

    // 1) A的連續區間最大長 v =max( B.v,C.v ,B.rlen+C.llen)
    // 2) A的llen  = B.llen 。 且當 B整隻為1 A.len += C.llen
    // 3) A的rlen = C.rlen  且當 C整隻為1  A.rlen += B.rlen

    int tmp = max(node[numb<<1].v, node[numb<<1|1].v);
    node[numb].v = max(tmp, node[numb<<1].rlen + node[numb<<1|1].llen);
    // 更新根節點的 區間內最長區間值
    node[numb].llen = node[numb<<1].llen;
    node[numb].rlen = node[numb<<1|1].rlen;

    if(node[numb<<1].v == node[numb<<1].r-node[numb<<1].l + 1)
        node[numb].llen +=  node[numb<<1|1].llen;

    if(node[numb<<1|1].v == node[numb<<1|1].r - node[numb<<1|1].l + 1)
        node[numb].rlen += node[numb<<1].rlen ;

}
void change(int l,int r,int numb,int val)   //  插入更新資料;
{
    if(node[numb].l==l&&node[numb].r==r)    //  如果區間完全重合,則不需要再往下更新了,先儲存起來,可以節約很多的時間(lazy思想)
    {
        node[numb].lazy=val;
        node[numb].changeLen();
        return;
    }
    if(node[numb].lazy!=-1) PushDown(numb);     //  因為沒有找到完全重合的區間,所以要先更新下一層區間;
    int mid=(node[numb].r+node[numb].l)>>1;

    if(l>mid) change(l,r,numb<<1|1,val);
    else if(r<=mid) change(l,r,numb<<1,val);
    else{
        change(l,mid,numb<<1,val);
        change(mid+1,r,numb<<1|1,val);
    }
    PushUp(numb);       //  最後還得往上返回,更新父節點區間;
}

query查詢

if 查詢到目標區間
    返回節點上的資訊 // 有時候是最大值,有時候是和
    retrurn;

if lazy 
    pushDown() // 向下找之前,有lazy標記必須先向下更新


mid = (node[numb].l + node[numb].r) / 2;

if(l>mid) // 進入右子樹
    return 查詢右子樹
else if(r<=mid) //進入左子樹
    return 查詢左子樹
else 
{
    // 左右子樹都佔一部分
    // 返回值 需要就題論題,
    int tmp1 = query(l,mid,numb<<1);
    int tmp2 = query(mid+1,r,numb<<1|1);

    // 求最大值 return max(tmp1,tmp2);
    // 求和 return tmp1+tmp2;
    
    // 不過本題有不一樣的需求
}

分析本題, 需求是找到 滿足區間的起始位置, 而且儘可能靠左。 那麼判斷條件就應是 v  ,llen ,rlen 

1、如果左兒子的v>=x那麼就返回query(左兒子)
2、如果左兒子的字尾+右兒子的字首>=x,直接返回左兒子的字尾開始的下標
3、若果右兒子的v>=x那麼就返回query(右兒子)
4、那就只能返回0了(憂桑~~>_<)

由此可見不同題目要求,判斷條件,返回條件也要順應著修改

int query(int l,int r,int numb, int len)
{
    if(node[numb].l==node[numb].r&&len == 1){
        return node[numb].l;
    }
    if(node[numb].lazy!=-1) PushDown(numb);    
    int mid=(node[numb].r+node[numb].l)>>1;
    // 若左邊區間大於等於val 則去左邊查
    // 若左邊的字尾 + 右邊的字首 >= val ,則返回左邊字尾的起點
    // 若右邊區間大於等於val ,則去右邊
    if(node[numb<<1].v >= len)
        return query(l,mid,numb<<1,len);
    if(node[numb<<1].rlen + node[numb<<1|1].llen >= len)
        return node[numb<<1].r - node[numb<<1].rlen + 1;
    if(node[numb<<1|1].v >= len)
        return query(mid+1,r,numb<<1|1, len);
    return 0;
}

完整程式碼

#include<iostream>
#include<string>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1000005;
int ans[N];
struct node
{
    int l,r,v,lazy,llen,rlen;
    void changeLen()
    {
        llen = rlen = v = lazy*(r-l+1);
    }
}node[N<<2];    //  線段樹的空間大概是陣列空間的4倍;

void build(int l,int r,int numb)    //  線段樹的建立;
{
    node[numb].lazy=1;
    node[numb].l = l;
    node[numb].r = r;
    node[numb].changeLen();
    if(l==r) return;
    int mid=(l+r)>>1;
    build(l,mid,numb<<1);
    build(mid+1,r,numb<<1|1);
}
void PushUp(int numb)               
{
    // 假設父節點是A ,左右子樹分別是BC
    // pushUp時 關心BC,對A的影響

    // 1) A的連續區間最大長 v =max( B.v,C.v ,B.rlen+C.llen)
    // 2) A的llen  = B.llen 。 且當 B整隻為1 A.len += C.llen
    // 3) A的rlen = C.rlen  且當 C整隻為1  A.rlen += B.rlen

    int tmp = max(node[numb<<1].v, node[numb<<1|1].v);
    node[numb].v = max(tmp, node[numb<<1].rlen + node[numb<<1|1].llen);
    // 更新根節點的 區間內最長區間值
    node[numb].llen = node[numb<<1].llen;
    node[numb].rlen = node[numb<<1|1].rlen;

    if(node[numb<<1].v == node[numb<<1].r-node[numb<<1].l + 1)
        node[numb].llen +=  node[numb<<1|1].llen;

    if(node[numb<<1|1].v == node[numb<<1|1].r - node[numb<<1|1].l + 1)
        node[numb].rlen += node[numb<<1].rlen ;

}
void PushDown(int numb)             //  向下往左右兒子方向更新資料;
{
    // 為了父節點的lazy 對左右子樹的影響,要思考lazy會影響左右子樹的什麼
    // 本題,lazy肯定要向下傳遞,子樹再根據lazy ,修改自身變數
    //(標記為0 相當於該區間完全被佔用,也就是llen rlen v = 0 )

    // 最後 把父節點的 lazy 標記取消 變成-1
    node[numb<<1].lazy=node[numb].lazy;
    node[numb<<1|1].lazy=node[numb].lazy;
    node[numb<<1].changeLen();
    node[numb<<1|1].changeLen();

    node[numb].lazy=-1;              //  更新完了要清零;
}



void change(int l,int r,int numb,int val)   //  插入更新資料;
{
    if(node[numb].l==l&&node[numb].r==r)    //  如果區間完全重合,則不需要再往下更新了,先儲存起來,可以節約很多的時間(lazy思想)
    {
        node[numb].lazy=val;
        node[numb].changeLen();
        return;
    }
    if(node[numb].lazy!=-1) PushDown(numb);     //  因為沒有找到完全重合的區間,所以要先更新下一層區間;
    int mid=(node[numb].r+node[numb].l)>>1;

    if(l>mid) change(l,r,numb<<1|1,val);
    else if(r<=mid) change(l,r,numb<<1,val);
    else{
        change(l,mid,numb<<1,val);
        change(mid+1,r,numb<<1|1,val);
    }
    PushUp(numb);       //  最後還得往上返回,更新父節點區間;
}

// 查詢到 連續區間>=len的 返回左邊第一個座標
int query(int l,int r,int numb, int len)
{
    if(node[numb].l==node[numb].r&&len == 1){
        return node[numb].l;
    }
    if(node[numb].lazy!=-1) PushDown(numb);    
    int mid=(node[numb].r+node[numb].l)>>1;
    // 若左邊區間大於等於val 則去左邊查
    // 若左邊的字尾 + 右邊的字首 >= val ,則返回左邊字尾的起點
    // 若右邊區間大於等於val ,則去右邊
    if(node[numb<<1].v >= len)
        return query(l,mid,numb<<1,len);
    if(node[numb<<1].rlen + node[numb<<1|1].llen >= len)
        return node[numb<<1].r - node[numb<<1].rlen + 1;
    if(node[numb<<1|1].v >= len)
        return query(mid+1,r,numb<<1|1, len);
    return 0;
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    build(1,n,1);
    while(m--)
    {
        int x,y,z;
        scanf("%d",&z);
        if(z==1){
            scanf("%d",&x),cout<<(y=query(1,n,1,x))<<endl;
            if(y!=0)
                change(y,y+x-1,1,0);

        }
        else
            scanf("%d%d",&x,&y),change(x,x+y-1,1,1);
    }

    return 0;
}

1、分析資料結構

1、lazy:  加1操作時,如果該節點以下的子節點+1 不會產生貢獻, 那麼本次操作寄存在該節點上,即lazy+1
2、num:  該節點管轄的區間下,還差多少能產生貢獻度,如果能產生貢獻度,那麼必須更新到葉子節點上
3、v : 貢獻度
4、b:  分母b陣列

2、更新操作

當查詢到目標區間,對目標區間內的num--,

如果減完的num >0 ,那麼本次操作寄存到lazy 。

當num ==0 ,則表明產生了貢獻,那麼此時需要更新到葉子節點

pushUp 保證了父節點的num、v 受到左右子節點影響,到底層更新節點,回溯回去後就更新。

void Insert(int l,int r,int numb)   //  插入更新資料;
{
    if(node[numb].l==l&&node[numb].r==r) // 找到目標區間 ,那麼區間上的 num--, 因為這段區間是被更新的
    {
        node[numb].num --;
        if(node[numb].num > 0)
        {
            // 還產生價值
            node[numb].lazy ++ ;// 找到目標區間,而且未產生價值,那麼這段區間以下的 加操作先存到lazy裡。
            return ;
        }
        else
        {
            if(node[numb].l == node[numb].r) // 當num == 0 , 則必須要更新到葉子節點
            {
                node[numb].v ++;
                node[numb].num = node[numb].b;
                node[numb].lazy = 0;
                return;
            }
        }

    }
    if(node[numb].lazy)
        PushDown(numb);
    int mid=(node[numb].r+node[numb].l)>>1;
    if(l>mid)
        Insert(l,r,numb<<1|1);
    else if(r<=mid)
        Insert(l,r,numb<<1);
    else
    {
        Insert(l,mid,numb<<1);
        Insert(mid+1,r,numb<<1|1);
    }
    PushUp(numb);
}

完整程式碼


#include<iostream>
#include<string>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1000005;
int ans[N];
int b[N];

struct node
{
    int l,r,v,lazy,b,num; // v: 值 ,b: 輸入的b陣列  num: 每個節點上 差多少能再得到一個滿足的 最小值
        // lazy 標記為該節點以下 ,還得加多少次, 先寄存

} node[N<<2];   //  線段樹的空間大概是陣列空間的4倍;

void build(int l,int r,int numb)    //  線段樹的建立;
{
    node[numb].l=l;
    node[numb].r=r;
    node[numb].v=0;
    node[numb].lazy=0;              //  用了lazy思想,提高了效率;
    if(l==r)
    {
        node[numb].b = b[l];
        node[numb].num = b[l];
        //cout<<b[l]<<' ';
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,numb<<1);
    build(mid+1,r,numb<<1|1);
    node[numb].num = min(node[numb<<1].num, node[numb<<1|1].num);
}
void PushUp(int numb)
{
    node[numb].v= node[numb<<1].v + node[numb<<1|1].v;
    node[numb].num = min(node[numb<<1].num,node[numb<<1|1].num );
}
void PushDown(int numb)             //  向下往左右兒子方向更新資料;
{
    node[numb<<1].lazy+=node[numb].lazy; //  寄存肯定是要 += 
    node[numb<<1|1].lazy+=node[numb].lazy;
    node[numb<<1].num-=node[numb].lazy;
    node[numb<<1|1].num-=node[numb].lazy;
    node[numb].lazy=0;              //  更新完了要清零;
}
void Insert(int l,int r,int numb)   //  插入更新資料;
{
    if(node[numb].l==l&&node[numb].r==r) // 找到目標區間 ,那麼區間上的 num--, 因為這段區間是被更新的
    {
        node[numb].num --;
        if(node[numb].num > 0)
        {
            // 還產生價值
            node[numb].lazy ++ ;// 找到目標區間,而且未產生價值,那麼這段區間以下的 加操作先存到lazy裡。
            return ;
        }
        else
        {
            if(node[numb].l == node[numb].r) // 當num == 0 , 則必須要更新到葉子節點
            {
                node[numb].v ++;
                node[numb].num = node[numb].b;
                node[numb].lazy = 0;
                return;
            }
        }

    }
    if(node[numb].lazy)
        PushDown(numb);
    int mid=(node[numb].r+node[numb].l)>>1;
    if(l>mid)
        Insert(l,r,numb<<1|1);
    else if(r<=mid)
        Insert(l,r,numb<<1);
    else
    {
        Insert(l,mid,numb<<1);
        Insert(mid+1,r,numb<<1|1);
    }
    PushUp(numb);
}
int query(int l,int r,int numb)
{
    if(node[numb].l==l&&node[numb].r==r)
    {
        return node[numb].v;
    }
    if(node[numb].lazy)
        PushDown(numb);
    int mid=(node[numb].r+node[numb].l)>>1;
    if(l>mid)
        return query(l,r,numb<<1|1);
    else if(r<=mid)
        return query(l,r,numb<<1);
    else
    {
        return query(l,mid,numb<<1) + query(mid+1,r,numb<<1|1) ;
    }
}
int main()
{
    char str[10];
    int x, y,n,m;
    while(~scanf("%d%d", &n, &m))
    {
        for(int i=1; i<=n; i++)
            scanf("%d",&b[i]);
        build(1, n, 1);
        for(int i = 0; i < m; i ++)
        {
            scanf("%s%d%d", str, &x, &y);
            if(str[0] == 'a')
            {
                Insert(x, y, 1);
            }
            else
                printf("%d\n", query(x, y, 1));
        }
    }
    return 0;
}
/*
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<math.h>
#include<cstdlib>
#include<stdlib.h>
#include<queue>
#include<map>
#include<set>
#include<stack>
#define bug printf("*********\n");
#define mem0(a) memset(a, 0, sizeof(a));
#define mem1(a) memset(a, -1, sizeof(a));
#define finf(a, n) fill(a, a+n, INF);
#define in1(a) scanf("%d" ,&a);
#define in2(a, b) scanf("%d%d", &a, &b);
#define in3(a, b, c) scanf("%d%d%d", &a, &b, &c);
#define out1(a) printf("%d\n", a);
#define out2(a, b) printf("%d %d\n", a, b);
#define pb(G, b) G.push_back(b);
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long LL;
typedef pair<LL, pair<int, LL> > LLppar;
typedef pair<int, int> par;
typedef pair<LL, int> LLpar;
const int mod = 998244353;
const LL INF = 1e9+7;
const int N = 1010;
const double pi = 3.1415926;

int n, m, mi;

struct node
{
    int l;
    int r;
    int num;  //節點當前狀態,為0時意味著達到整除狀態
    int lazy; //懶惰標記
    int sum; //整除結果
    int b; //b陣列初始值
}e[100010*4];

void build(int l, int r, int k)
{
    e[k].l = l;
    e[k].r = r;
    e[k].sum = 0;
    e[k].lazy = 0;
    if(l == r) {
        scanf("%d", &e[k].b);
        e[k].sum = 0;
        e[k].num = e[k].b;
        return;
    }
    int mid = (l+r)/2;
    build(l, mid, 2*k);
    build(mid+1, r, 2*k+1);
    e[k].num = min(e[2*k].num, e[2*k+1].num); //維護區間最小值
}

void push(int k)
{
    if(e[k].lazy) {
        e[2*k].num += e[k].lazy;
        e[2*k+1].num += e[k].lazy;
        e[2*k].lazy += e[k].lazy;
        e[2*k+1].lazy += e[k].lazy;
        e[k].lazy = 0;
    }
}

void update(int l, int r, int k)
{
    if(e[k].l == l && e[k].r == r) {
        e[k].num --;
        if(e[k].num) {
        e[k].lazy --; //該區間沒有可以整除的節點
            return;
        }else {
            if(e[k].l == e[k].r) { //找到達到整除狀態的節點
                e[k].sum ++;
                e[k].num = e[k].b; //更新
                return;
            }
        }
    }
    push(k);
    int mid = (e[k].l+e[k].r)/2;
    if(r <= mid) update(l, r, 2*k);
    else if(l > mid) update(l, r, 2*k+1);
    else {
        update(l, mid, 2*k);
        update(mid+1, r, 2*k+1);
    }
    e[k].num = min(e[2*k].num, e[2*k+1].num); //維護區間最小值
    e[k].sum = e[2*k].sum + e[2*k+1].sum;
}

int quary(int l, int r, int k)
{
    if(e[k].l == l && e[k].r == r) {
        return e[k].sum;
    }
    int mid = (e[k].l+e[k].r)/2;
    if(r <= mid) return quary(l, r, 2*k);
    else if(l > mid) return quary(l ,r, 2*k+1);
    else {
        return quary(l, mid, 2*k) + quary(mid+1, r, 2*k+1);
    }
}

int main()
{
    char str[10];
    int x, y;
    while(~scanf("%d%d", &n, &m)) {
        build(1, n, 1);
        for(int i = 0; i < m; i ++) {
            scanf("%s%d%d", str, &x, &y);
            if(str[0] == 'a') {
                update(x, y, 1);
            }else printf("%d\n", quary(x, y, 1));
        }
    }
    return 0;
}
*/

手殘居然求和、最大值寫不出來,我恨!

#include<iostream>
#include<string>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1000005;
int ans[N];
struct node
{
    int l,r,v,lazy,sum,maxx;
    void changeLen()
    {
        sum += lazy*(r-l+1);
        maxx += lazy;
    }
} node[N<<2];   //  線段樹的空間大概是陣列空間的4倍;

void build(int l,int r,int numb)    //  線段樹的建立;
{
    node[numb].lazy=0;
    node[numb].l = l;
    node[numb].r = r;
    node[numb].changeLen();
    if(l==r)
        return;
    int mid=(l+r)>>1;
    build(l,mid,numb<<1);
    build(mid+1,r,numb<<1|1);
}
void PushUp(int numb)
{
    node[numb].sum += node[numb<<1].sum + node[numb<<1|1].sum;
    node[numb].maxx = max(node[numb<<1].maxx , node[numb<<1|1].maxx);
}
void PushDown(int numb)             //  向下往左右兒子方向更新資料;
{
    node[numb<<1].lazy=node[numb].lazy;
    node[numb<<1|1].lazy=node[numb].lazy;
    node[numb<<1].changeLen();
    node[numb<<1|1].changeLen();

    node[numb].lazy=0;              //  更新完了要清零;
}

void change(int l,int r,int numb,int val)   //  插入更新資料;
{
    if(node[numb].l==l&&node[numb].r==r)    //  如果區間完全重合,則不需要再往下更新了,先儲存起來,可以節約很多的時間(lazy思想)
    {
        node[numb].lazy=val;
        node[numb].changeLen();
        return;
    }
    if(node[numb].lazy!=0)
        PushDown(numb);     //  因為沒有找到完全重合的區間,所以要先更新下一層區間;
    int mid=(node[numb].r+node[numb].l)>>1;

    if(l>mid)
        change(l,r,numb<<1|1,val);
    else if(r<=mid)
        change(l,r,numb<<1,val);
    else
    {
        change(l,mid,numb<<1,val);
        change(mid+1,r,numb<<1|1,val);
    }
    PushUp(numb);       //  最後還得往上返回,更新父節點區間;
}

// 查詢到 連續區間>=len的 返回左邊第一個座標
int query(int l,int r,int numb)
{
    if(node[numb].l==node[numb].r)
    {
        return node[numb].sum;
    }
    if(node[numb].lazy!=0)
        PushDown(numb);
    int mid=(node[numb].r+node[numb].l)>>1;

    if(r <= mid)
        return query(l,r,numb<<1);
    else if(l > mid)
        return query(l,r,numb<<1|1);
    else
    {
        return query(l,mid,numb<<1) + query(mid+1, r, numb<<1|1);
    }
}

int queryMax(int l,int r, int numb)
{
      if(node[numb].l==node[numb].r)
    {
        return node[numb].maxx;
    }
    if(node[numb].lazy!=0)
        PushDown(numb);
    int mid=(node[numb].r+node[numb].l)>>1;

     if(r <= mid)
        return queryMax(l,r,numb<<1);
    else if(l > mid)
        return queryMax(l,r,numb<<1|1);
    else
    {
        return  max( queryMax(l,mid,numb<<1) , queryMax(mid+1, r, numb<<1|1));
    }
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    build(1,n,1);
    while(m--)
    {
        int x,y,z;
        scanf("%d",&z);
        if(z==1)
        {
            scanf("%d%d",&x,&y);
            cout<<query(x,y,1)<<endl;
        }
        else if(z == 2)
        {
            scanf("%d%d",&x,&y);
            cout<<queryMax(x,y,1)<<endl;
        }
        else
            scanf("%d%d",&x,&y),change(x,y,1,1);
    }

    return 0;
}