线段树 学习笔记

简介

略。

-1 一个模板

改编自 tourist。其中只需要写 apply、pushdown、merge

class segtree {
public:
    struct node {
        // don't forget to set default value (used for leaves)
        // not necessarily neutral element!
        int sum=0,add=0;
        void apply(int l,int r,... v) {
            //sum+=(r-l+1)*v,add+=v;
        }
    };

    node merge(const node &a,const node &b) const {
        node res;
        res.sum=a.sum+b.sum;
        return res;
    }

    inline void pushdown(int x,int l,int r) {
        int y=(l+r)>>1;
        int z=x+((y-l+1)<<1);
        // pushdown from x into (x + 1) and z
        if(tree[x].add!=0) {
            tree[x+1].apply(l,y,tree[x].add);
            tree[z].apply(y+1,r,tree[x].add);
            tree[x].add=0;
        }
    }

    inline void pushup(int x,int z) {
        tree[x]=merge(tree[x+1],tree[z]);
    }

    int n;
    vector<node> tree;

    void build(int x,int l,int r) {
        if(l==r) {
            return;
        }
        int y=(l+r)>>1,z=x+((y-l+1)<<1);
        build(x+1,l,y),build(z,y+1,r);
        pushup(x,z);
    }

    template <typename M>
    void build(int x,int l,int r,const vector<M> &v) {
        if(l==r) {
            tree[x].apply(l,r,v[l]);
            return;
        }
        int y=(l+r)>>1,z=x+((y-l+1)<<1);
        build(x+1,l,y,v),build(z,y+1,r,v);
        pushup(x,z);
    }

    node get(int x,int l,int r,int ll,int rr) {
        if(ll<=l&&r<=rr) {
            return tree[x];
        }
        int y=(l+r)>>1,z=x+((y-l+1)<<1);
        pushdown(x,l,r);
        node res{};
        if(rr<=y) res=get(x+1,l,y,ll,rr);
        else if(ll>y) res=get(z,y+1,r,ll,rr);
        else res=merge(get(x+1,l,y,ll,rr),get(z,y+1,r,ll,rr));
        pushup(x,z);
        return res;
    }

    template <typename... M>
    void modify(int x,int l,int r,int ll,int rr,const M&... v) {
        if(ll<=l&&r<=rr) {
            tree[x].apply(l,r,v...);
            return;
        }
        int y=(l+r)>>1,z=x+((y-l+1)<<1);
        pushdown(x,l,r);
        if(ll<=y) modify(x+1,l,y,ll,rr,v...);
        if(rr>y) modify(z,y+1,r,ll,rr,v...);
        pushup(x,z);
    }

    int find_first_knowingly(int x,int l,int r,const function<bool(const node&)> &f) {
        if(l==r) return l;
        pushdown(x,l,r);
        int y=(l+r)>>1,z=x+((y-l+1)<<1);
        int res;
        if(f(tree[x+1])) res=find_first_knowingly(x+1,l,y,f);
        else res=find_first_knowingly(z,y+1,r,f);
        pushup(x,z);
        return res;
    }

    int find_first(int x,int l,int r,int ll,int rr,const function<bool(const node&)> &f) {
        if(ll<=l&&r<=rr) {
            if(!f(tree[x])) return -1;
            return find_first_knowingly(x,l,r,f);
        }
        pushdown(x,l,r);
        int y=(l+r)>>1;
        int z=x+((y-l+1)<<1);
        int res=-1;
        if(ll<=y) res=find_first(x+1,l,y,ll,rr,f);
        if(rr>y&&res==-1) res=find_first(z,y+1,r,ll,rr,f);
        pushup(x,z);
        return res;
    }

    int find_last_knowingly(int x,int l,int r,const function<bool(const node&)> &f) {
        if(l==r) {
            return l;
        }
        pushdown(x,l,r);
        int y=(l+r)>>1,z=x+((y-l+1)<<1);
        int res;
        if(f(tree[z])) res=find_last_knowingly(z,y+1,r,f);
        else res=find_last_knowingly(x+1,l,y,f);
        pushup(x,z);
        return res;
    }

    int find_last(int x,int l,int r,int ll,int rr,const function<bool(const node&)> &f) {
        if(ll<=l&&r<=rr) {
            if(!f(tree[x])) return -1;
            return find_last_knowingly(x,l,r,f);
        }
        pushdown(x,l,r);
        int y=(l+r)>>1,z=x+((y-l+1)<<1);
        int res=-1;
        if(rr>y) res=find_last(z,y+1,r,ll,rr,f);
        if(ll<=y&&res==-1) res=find_last(x+1,l,y,ll,rr,f);
        pushup(x,z);
        return res;
    }

    segtree(int _n): n(_n) {
        assert(n>0);
        tree.resize(2*n-1);
        build(0,0,n-1);
    }

    template <typename M>
    segtree(const vector<M> &v) {
        n=v.size();
        assert(n>0);
        tree.resize(2*n-1);
        build(0,0,n-1,v);
    }

    node get(int ll,int rr) {
        assert(0<=ll&&ll<=rr&&rr<=n-1);
        return get(0,0,n-1,ll,rr);
    }

    node get(int p) {
        assert(0<=p&&p<=n-1);
        return get(0,0,n-1,p,p);
    }

    template <typename... M>
    void modify(int ll,int rr,const M&... v) {
        assert(0<=ll&&ll<=rr&&rr<=n-1);
        modify(0,0,n-1,ll,rr,v...);
    }

    // find_first and find_last call all FALSE elements
    // to the left (right) of the sought position exactly once

    int find_first(int ll,int rr,const function<bool(const node&)> &f) {
        assert(0<=ll&&ll<=rr&&rr<=n-1);
        return find_first(0,0,n-1,ll,rr,f);
    }

    int find_last(int ll,int rr,const function<bool(const node&)> &f) {
        assert(0<=ll&&ll<=rr&&rr<=n-1);
        return find_last(0,0,n-1,ll,rr,f);
    }
};

0. 一些简单的约定

令 ls 表示左儿子,rs 表示右儿子,mid 表示区间中点(即 \((l+r)/2\)

1. 一般线段树的常见优化技巧

1.1 动态开点线段树

对于普通的线段树节点 \(x\),定义其左儿子的编号为 \(2x\),右儿子的编号为 \(2x+1\)

但是如果在长度为 \(1e9\) 的序列中做区间加区间求和,普通线段树无法高效地解决问题,因此产生动态开点线段树。

对于每个节点,维护其 ls 和 rs 的编号,修改下传时若对应儿子为空就新建一个节点,可以通过传递引用值方便地实现。查询时如果走到空节点就直接返回。

1.2 两倍空间线段树

令区间 \([l,r]\) 的编号为 \((l+r)\operatorname{or}[l\ne r]\)

一个节点 \([x,l,r]\) 的左儿子编号为 \(mid\times 2\),右儿子编号为 \(mid \times 2+1\)

容易证明这是两倍空间。

1.3 标记永久化

考虑每次不递归,直接打标记,这样省去了 pushdown 的常数,但是需要在 upd 和 query 时计算当前区间和查询区间的贡献。

2. 权值线段树

类似线段树维护一个桶,可以支持单点插入,区间查询 x 的排名,第 k 小(线段树二分)。

3. 线段树合并

考虑如果能快速合并叶子节点的信息和 pushup,就能合并快速合并两棵线段树。
动态开点,对空区间返回,否则合并叶子节点之后 pushup。

int merge(int l,int r,int x,int y) {
    if(!x||!y) return x|y;//or x^y.
    int z=++cnt;
    if(l==r) return /* 合并叶子 x 和 y */,z;
    ls[z]=merge(l,mid,ls[x],ls[y]);
    rs[z]=merge(mid+1,r,rs[x],rs[y]);
    return /*pushup*/,z;
}

参考资料:
1

2

posted @ 2024-03-11 11:17  lgh_2009  阅读(13)  评论(0编辑  收藏  举报