线段树 学习笔记
简介
略。
-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 表示区间中点(即
1. 一般线段树的常见优化技巧
1.1 动态开点线段树
对于普通的线段树节点
但是如果在长度为
对于每个节点,维护其 ls 和 rs 的编号,修改下传时若对应儿子为空就新建一个节点,可以通过传递引用值方便地实现。查询时如果走到空节点就直接返回。
1.2 两倍空间线段树
令区间
一个节点
容易证明这是两倍空间。
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
本文作者:lgh_2009
本文链接:https://www.cnblogs.com/lgh-blog/p/18061312
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步