线段树

· · 算法·理论

0. 前置芝士:二叉树的存储。

如果给一棵完全二叉树从左往右、从上往下标号,那么 i 号节点的父亲编号为 \lfloor \dfrac{i}{2} \rfloor,左儿子编号为 2i,右儿子编号为 2i+1
那么我们可以用一个数组 a[i] 来存储一颗二叉树
注意,这种方法最多需要开 2^n 个节点。线段树中为 4n

删改自blog基本树

1. 线段树例题1:【模板】线段树

线段树的本质:将若干个区间用树上结点表示。
例如区间 [l,r],我们记 mid=\left \lfloor \dfrac{l+r}{2}\right \rfloor,将 [l,mid][mid+1,r] 作为它的左右儿子。
例如一张长度为 10 的序列,存储下来像这样:

线段树有以下性质:

接下来,换道题休息一下:

2. 线段树例题2:开关

这里很简单,区间异或就是将 01 互换。所以稍微变动一下 \text{maketag} 函数便可:

void maketag(int u,int len,long long x)
{
        lzy[u]^=1;
        w[u]=len-w[u];
}

最后说一下线段树的使用范围:“可加性”

什么意思呢?加上 a,加上 b,相当于加上 a+b;乘上 x,乘以 y,相当于乘上 x\times y……即先做 A,再做 B,相当于一起做了 AB。也可以看成有结合律的运算,如 x+a+b=x+(a+b),x\oplus a\oplus b=x\oplus (a\oplus b),原因就不给了,可以在懒标记中发现。

推荐练习:P1438,P1253,P3373,P1908。

3. 动态开点

如果你需要维护一个 [1,10^9] 的区间,你或许会掏出离散化。但如果它强制在线呢?动态开点!

动态开点的宗旨是:要用什么点就建立什么节点。以前来说,u 的两个儿子是 2u2u+1,但现在变成了 ls,rs

由于每次操作的时间复杂度是 O(\log n),每次操作就可能创建 O(\log n) 级别的点。操作 m 次后,也只会创造 O(m\log n) 个节点。时间复杂度同样还是 O(\log n) 一次。

?.菜单——线段树

最后的最后,你肯定很讨厌定义线段树。所以这里给一个线段树:

const int maxn=500006;//依情况修改
struct Segment{//依情况修改
    long long a[maxn],w[4*maxn],lzy[4*maxn];
    void pushup(int u)
    {
        w[u]=w[u*2]+w[u*2+1];
    }
    void build(int u=1,int l=1,int r=n)
    {
        if(l==r)
        {
            w[u]=a[l];
            return;
        }
        int m=(l+r)/2;
        build(u*2,l,m),build(u*2+1,m+1,r);
            pushup(u);
    }
    bool InRange(int L,int R,int l,int r)
    {
        return (l<=L)&&(R<=r);
    }
    bool OutofRange(int L,int R,int l,int r)
    {
        return (L>r)||(R<l);
    }
    void maketag(int u,int len,long long x)
    {
        lzy[u]+=x;
        w[u]+=len*x;
    }
    void pushdown(int u,int l,int r)
    {
        int m=(l+r)/2;
        maketag(u*2,m-l+1,lzy[u]);
        maketag(u*2+1,r-m,lzy[u]);
        lzy[u]=0;
    }
    int query(int l,int r,int u=1,int L=1,int R=n)
    {
        if(InRange(L,R,l,r)) return w[u];
        else if(!OutofRange(L,R,l,r))
        {
          int m=(L+R)/2;
          pushdown(u,L,R);
        return query(l,r,u*2,L,m)+query(l,r,u*2+1,m+1,R);
        }
        else return 0;
    }
    void update(int l,int r,long long x,int u=1,int L=1,int R=n)
    {
        if(InRange(L,R,l,r)) maketag(u,R-L+1,x);
        else if(!OutofRange(L,R,l,r))
        {
            int m=(L+R)/2;
            pushdown(u,L,R);
            update(l,r,x,u*2,L,m);
            update(l,r,x,u*2+1,m+1,R);
            pushup(u);
        }
    }
};

以上是无空格版

以下是空格版

const int maxn = 500006;
struct Segment{
    long long a[maxn],w[4*maxn],lzy[4*maxn];
    void pushup(int u){
        w[u] = w[u * 2] + w[u * 2 + 1];
    }
    void build(int u = 1, int l = 1, int r = n){
        if(l == r)
        {
            w[u] = a[l];
            return;
        }
        int m = (l + r) / 2;
        build(u * 2, l, m);
        build(u * 2 + 1, m + 1, r);
        pushup(u);
    }
    bool InRange(int L, int R, int l, int r){
        return (l <= L) && (R <= r);
    }
    bool OutofRange(int L, int R, int l, int r){
        return (L > r) || (R < l);
    }
    void maketag(int u, int len, long long x){
        lzy[u] += x;
        w[u] += len * x;
    }
    void pushdown(int u, int l, int r){
        int m = (l + r) / 2;
        maketag(u * 2, m - l + 1, lzy[u]);
        maketag(u * 2 + 1, r - m, lzy[u]);
        lzy[u] = 0;
    }
    int query(int l, int r, int u = 1, int L = 1, int R = n)
    {
        if(InRange(L, R, l, r)) return w[u];
        else if(!OutofRange(L, R, l, r)){
            int m = (L + R) / 2;
            pushdown(u, L, R);
            return query(l, r, u * 2, L, m) + query(l, r, u * 2 + 1, m + 1, R);
        }
        else return 0;
    }
    void update(int l, int r, long long x, int u = 1, int L = 1, int R = n){
        if(InRange(L, R, l, r)) maketag(u, R - L + 1, x);
        else if(!OutofRange(L, R, l, r)){
            int m = (L + R) / 2;
            pushdown(u, L, R);
            update(l, r, x, u * 2, L, m);
            update(l, r, x, u * 2 + 1, m + 1, R);
            pushup(u);
        }
    }
};

怎么使用呢?

定义:Segment a;

使用:构造 a.build(),区间修改 a.update(x,y,k),区间查询 a.query(x,y)。其中,x,y 为区间,k 为修改值。注意有些函数的值顺序改了一下,记得调回来。此份代码是求区间和的代码。

?.菜单——动态开点线段树

const int maxn=100005;
int n;
struct dt_Segment_tree{
    struct node{
        int l,r,val,lzy;
        node(){
            l=r=val=lzy=0;
        }
    }tr[30*maxn];
    int tot=0;
    dt_Segment_tree(){
        tot++; // 建立根节点,可以直接在定义 tot 时写 'int tot=1;'
    }
    void pushup(int u)
    {
        tr[u].val=tr[tr[u].l].val+tr[tr[u].r].val;
    }
    void maketag(int u,int len,int x)
    {
        tr[u].lzy+=x;
        tr[u].val+=len*x;
    }
    void pushdown(int u,int l,int r)
    {
        if(!tr[u].l) tr[u].l=++tot;
        if(!tr[u].r) tr[u].r=++tot;
        int mid=(l+r)/2;
        maketag(tr[u].l,mid-l+1,tr[u].lzy);
        maketag(tr[u].r,r-mid,tr[u].lzy);
        tr[u].lzy=0;
    }
    bool InRangeOf(int L,int R,int l,int r)
    {
        return (l<=L)&&(R<=r);
    }
    bool OutRangeOf(int L,int R,int l,int r)
    {
        return (L>r)||(R<l);
    }
    void update(int l,int r,int x,int u=1,int L=1,int R=n)
    {
        if(InRangeOf(L,R,l,r)) maketag(u,R-L+1,x);
        else if(!OutRangeOf(L,R,l,r))
        {
            pushdown(u,L,R);
            int mid=(L+R)>>1;
            update(l,r,x,tr[u].l,L,mid);
            update(l,r,x,tr[u].r,mid+1,R);
            pushup(u);
        }
    }
    int query(int l,int r,int u=1,int L=1,int R=n)
    {
        if(InRangeOf(L,R,l,r)) return tr[u].val;
        else if(!OutRangeOf(L,R,l,r))
        {
            pushdown(u,L,R);
            int mid=(L+R)>>1;
            return query(l,r,tr[u].l,L,mid)+query(l,r,tr[u].r,mid+1,R);
        }
        else return 0;
    }
}

怎么使用呢?

定义:Segment a;

使用:区间修改 a.update(x,y,k),区间查询 a.query(x,y)。其中,x,y 为区间,k 为修改值。注意有些函数的值顺序改了一下,记得调回来。此份代码是求区间和的代码。