線段樹

學前班的魚同學 - 線段樹視頻

- 線段樹 -

線段樹:

線段樹(Segment Tree)是一種基於分治思想二叉樹結構,用於在區間上進行信息統計區間可加減性是它的一個重要性質。與按照二進制位(2的次冪)進行區間劃分的樹狀數組相比,線段樹是一種更加通用的結構,樹狀數組能做的線段樹都能做,不過樹狀數組會更快一些,需要的內存也比線段樹小。存儲線段樹的數組一般要開到題目中數據的四倍大小才能確保夠大:
1、線段樹的每個節點都代表一個區間
2、線段樹具有唯一的根節點,代表的區間是整個統計範圍,如 [1,N];
3、線段樹的每個葉節點都代表一個長度爲1的元區間 [x,x];
4、對於每個內部節點 [l,r],它的左子節點是 [l,mid],右子節點是 [mid+1,r],其中 mid=(l+r)/2 (向下取整)。

一、給定一個長度爲n的序列,有兩個操作,第一個在區間[l,r]上增加x,第二個詢問區間[l,r]的和:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

int a[100005];
int d[100005<<2], b[100005<<2];//區間和,lazy標記

inline void pushup(int p) {//標記上傳
    d[p]=d[p<<1]+d[(p<<1)|1];//d[p]=d[p*2]+d[p*2+1]
    return ;

inline void pushdown(int s, int t, int p) {//標記下傳
    int mid=(s+t)>>1;
    d[p<<1]+=(mid-s+1)*b[p];
    d[(p<<1)|1]+=(t-mid)*b[p];
    b[p<<1]+=b[p];
    b[(p<<1)|1]+=b[p];
    b[p]=0;
}

inline void build(int s, int t, int p) {//建樹
    if(s==t) {//若是葉子結點
        d[p]=a[s];//區間和=它本身
        return ;
    }
    int mid=(s+t)>>1;//mid=(s+t)/2區間中點
    build(s, mid, p<<1);//左子節點
    build(mid+1, t, (p<<1)|1);//右子節點
    pushup(p);//更新父節點
}

inline void update(int l, int r, int c, int s, int t, int p) {
    //把區間[l,r]加c,p是點號,s和t是這個點號代表的區間
    if(1<=s && t<=r) {//若是l~r的子區間
        d[p]+=(t-s+1)*c;
        b[p]+=c;//標記
        return ;
    }
    //若沒有完全覆蓋
    if(b[p]) pushdown(s, t, p);//若有標記,則要即時下傳
    int mid=(s+t)>>1;
    if(l<=mid) update(l, r, c, s, mid, p<<1);//左子節點有部分要更新
    if(r>mid) update(l, r, c, mid+1, t, (p<<1)|1);//右子節點有部分要更新
    pushup(p);//更新父節點
}

inline int getans(int l, int r, int s, int t, int p) {
    //查詢[l,r],p是節點,s和t是這個節點對應的區間
    int ans=0;
    if(l<=s && t<=r) {//完全包含
        return d[p];
    }
    //否則訪問子節點
    if(b[p]) pushdown(s, t, p);
    int mid=(s+t)>>1;
    if(l<=mid) ans+=getans(l, r, s, mid, p<<1);//如果在左邊
    if(r>mid) ans+=getans(l, r, mid+1, t, (p<<1)|1);
    return ans;
}

int main() {
    int n, m;//n是區間長度,m是操作個數
    scanf("%d %d", &n, &m);
    for(register int i=1; i<=n; i++) scanf("%d", &a[i]);//輸入a[i]
    build(1, n, 1);//開始建樹
    for(register int i=1; i<=m; i++) {
        int opt=0;//操作
        scanf("%d", &opt);
        if(opt==1) {//在區間[l,r]上增加c
            int l, r, c;
            scanf("%d %d %d", &l, &r, &c);
            update(l, r, c, 1, n, 1);//更新區間, 增加值,區間,節點
        }
        if(opt==2) {//詢問區間[l,r]的和
            int l, r;
            scanf("%d %d", &l, &r);
            printf("%d\n", getans(l, r, 1, n, 1));//求和並輸出結果
        }
    }
    return 0;
}

二、給定一個長度爲n的序列,一開始全是0,有兩個操作,一個是將[l,r]全改成c,一個是詢問[l,r]不同的數字個數:(考慮邊界)

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

int a[100005<<2], num[100005<<2], b[100005<<2], pre[100005<<2], las[100005<<2];
//數組,區間不同的顏色(數字)個數,lazy標記,區間前面的數,區間後面的數

inline void pushup(int p) {//標記上傳
    if(las[p<<1]==pre[(p<<1)|1]) num[p]=num[p<<1]+num[(p<<1)|1]-1;
    //若左子節點後面=右子節點前面,則不同顏色數爲左右子節點之和減一
    else num[p]=num[p<<1]+num[(p<<1)|1];//否則
    pre[p]=pre[p<<1];
    las[p]=las[(p<<1)|1];
}

inline void pushdown(int s, int t, int p) {//標記下傳
    int mid=(s+t)>>1;
    num[p<<1]=1;
    num[(p<<1)|1]=1;
    pre[p<<1]=pre[p];
    las[p<<1]=las[p];
    pre[(p<<1)|1]=pre[p];
    las[(p<<1)|1]=las[p];
    b[p<<1]=b[p];
    b[(p<<1)|1]=b[p];
    b[p]=0;
}

inline void build(int s, int t, int p) {//建樹
    if(s==t) {//若是葉子節點
        num[p]=1;
        pre[p]=0;
        las[p]=0;
        return ;
    }
    int mid=(s+t)>>1;
    build(s, mid, p<<1);
    build(mid+1, t, (p<<1)|1);
    pushup(p);
}

inline void update(int l, int r, int c, int s, int t, int p) {//更新區間[l,r]的信息
    if(l<=s && t<=r) {
        num[p]=1;
        las[p]=pre[p]=c;
        b[p]=c;
        return ;
    }
    if(b[p]) pushdown(s, t, p);
    int mid=(s+t)>>1;
    if(l<=mid) update(l, r, c, s, mid, p<<1);
    if(r>mid) update(l, r, c, mid+1, t, (p<<1)|1);
    pushup(p);
}

inline int getans(int l, int r, int s, int t, int p) {
    if(l<=s && t<=r) {
        return num[p];
    }
    if(b[p]) pushdown(s, t, p);
    int mid=(s+t)>>1;
    int ans=0;
    if(l<=mid) ans+=getans(l, r, s, mid, p<<1);
    if(r>mid) ans+=getans(l, r, mid+1, t, (p<<1)|1);
    if(las[p<<1]==pre[(p<<1)|1]) ans--;
    return ans;
}

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    build(1, n, 1);
    for(register int i=1; i<=m; i++) {
        int opt, l, r;
        scanf("%d", &opt);
        if(opt==0) {
            int c;
            c=0;
            scanf("%d %d %d", &l, &r, &c);
            update(l, r, c, 1, n, 1);
        }
        if(opt==1) {
            scanf("%d %d", &l, &r);
            printf("%d\n", getans(l, r, 1, n, 1));//求區間不同數字的個數並輸出答案
        }
    }
    return 0;
}

三、給定一個長度爲n的序列,詢問[l,r]的最大子段和:
*最大子段和:[l,r]的最大子段和爲存在一個區間[x,y]屬於[l,r]滿足對於任意屬於[l,r]滿足sum(i,j)<=sum(x,y)

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

template<typename sp>
inline void qread(sp &x) {
    char ch=getchar(), lst=' ';
    while(ch<'0' || ch>'9') lst=ch, ch=getchar();
    while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48), ch=getchar();
    if(lst=='-') x=-x;
}

template<typename sp>
sp mmax(sp a, sp b) {
    return a>b ? a : b;
}

int a[100005];
struct Node{
    int ans, pre, las, sum;//最大子段和,區間左邊部分的和,區間右邊部分的和,整個區間的和
}d[100005<<2];

inline void pushup(int p) {//上傳數據更新
    d[p].ans=mmax(mmax(d[p<<1].ans, d[(p<<1)|1].ans), d[(p<<1)|1].pre+d[p<<1].las);
    d[p].pre=mmax(d[p<<1].pre, d[p<<1].sum+d[(p<<1)|1].pre);
    d[p].las=mmax(d[(p<<1)|1].las, d[(p<<1)|1].sum+d[p<<1].las);
    d[p].sum=d[p<<1].sum+d[(p<<1)|1].sum;
}

inline void build(int s, int t, int p) {//建樹
    if(s==t) {
        d[p].ans=a[s];
        d[p].pre=a[s];
        d[p].las=a[s];
        d[p].sum=a[s];
        return ;
    }
    int mid=(s+t)>>1;
    build(s, mid, p<<1);
    build(mid+1, t, (p<<1)|1);
    pushup(p);
}

inline Node getans(int l, int r, int s, int t, int p) {//區間查詢
    if(l<=s && t<=r) return d[p];
    int mid=(s+t)>>1;
    if(r<=mid) return getans(l, r, s, mid, p<<1);
    else if(l>mid) return getans(l, r, mid+1, t, (p<<1)|1);
    else {
        Node ans;
        Node lans=getans(l, r, s, mid, p<<1), rans=getans(l, r, mid+1, t, (p<<1)|1);
        ans.pre=mmax(lans.pre, lans.sum+rans.pre);
        ans.las=mmax(rans.las, rans.sum+lans.las);
        ans.ans=mmax(mmax(rans.ans, lans.ans), lans.las+rans.pre);
        return ans;
    }
}

int main() {
    int n, m;
    qread(n); qread(m);
    for(register int i=1; i<=n; i++) qread(a[i]);
    build(1, n, 1);
    for(register int i=1; i<=n; i++) {
        int l, r;
        qread(l); qread(r);
        printf("%d\n", getans(l, r, 1, n, 1).ans);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章