线段树的一些延伸
一.动态开点线段树
简介
虽然思路简单,但对于一个习惯数组写法的人,这是一个比较难受的东西。
动态开点一般是用来解决空间上的问题的。
一般来说,普通的线段树是直接将一颗完整的线段建出来,但如碰到数据范围大或卡空间的时候,我们就只能在我们需要的时候再建,这个就叫做动态开点。(类似于 trie)
处理方法
- 结构体
动态开点用数组是极不方便的,所以采用结构体写法。(本人真的很不习惯)
struct Tree { int sum; int l,r; }t[MAXN<<5];
- 上传
inline void pushup(int p) { t[p].sum=t[t[p].l].sum+t[t[p].r].sum; return; }
- 下传
由于跑到的节点可能没建过,所以要新建。
inline void pushdown(int p) { if(!t[p].l) t[p].l=++tot; if(!t[p].r) t[p].r=++tot; return; }
- 单点修改
一般来说,我们这里算 是 mid=(l+r-1)>>1
,这样可以处理 均为负数的情况。
inline void change(int p,int l,int r,int x,int k) { if(l==r) {t[p].sum+=k;return;} int mid=(l+r-1)>>1; pushdown(p); if(x<=mid) change(t[p].l,l,mid,x,k); else change(t[p].r,mid+1,r,x,k); pushup(p); return; }
- 求和
inline int ask(int p,int l,int r,int a,int b) { if(l>b || r<a) return 0; if(l>=a && r<=b) return t[p].sum; int mid=(l+r-1)>>1,ans=0; pushdown(p); if(a<=mid) ans+=ask(t[p].l,l,mid,a,b); if(b>mid) ans+=ask(t[p].r,mid+1,r,a,b); return ans; }
二.权值线段树
简介
对于值域建立的线段树,用于统计区间内某个数出现次数。
支持维护同一个动态区间第 小(反之同理),支持单点修改。
修改与查询的复杂度均为 。
处理方法
- 建树
建树只需维护左右区间,无需赋值。
1. 用每个节点维护原序列中的值;
2. 记录每个区间每个数的出现次数;
inline void build(int p,int l,int r) { t[p].l=l,t[p].r=r; if(l==r) return; int mid=(l+r)>>1; build(p<<1,l,mid); build(p<<1|1,mid+1,r); return; }
特别地,因为权值线段树又名值域线段树,所以建树是基于值域建的。
如:
题目规定:
那么建树操作便为 build(1,1,100000)
。
- 修改
权值线段树在修改中可以维护区间第 小于前 小的和,故需要统计一下。
inline void change(int p,int k) { t[p].val+=k,t[p].num++; if(t[p].l==t[p].r) return; if(k>=t[p<<1|1].l) change(p<<1|1,k); else change(p<<1,k); return; }
- 查询
有了上面统计的那些信息,区间求值就很容易了。
区间前 小之和
inline int ask1(int p,int k) { if(t[p].l==t[p].r) return t[p].val/t[p].num*k; if(k>t[p<<1].num) return t[p<<1].val+ask1(p<<1|1,k-t[p<<1].num); else return ask1(p<<1,k); }
区间第 小
inline int ask2(int p,int k) { if(t[p].l==t[p].r) return t[p].val/t[p].num; if(k>t[p<<1].num) return ask2(p<<1|1,k-t[p<<1].num); else return ask2(p<<1,k); }
三.主席树
简介
又名可持久化权值线段树,用于动态维护任意区间第 大。
支持单点修改和区间查询。
处理方法
主席树的思想是对每一个前缀均建立一颗权值线段树。
- 初始化
我们发现,由于空间复杂度的问题,我们需要动态开点,所以就无法直接算出一个节点的左右儿子,所以都要记录一下。
需要注意的是,主席树一般要开32倍空间。
为版本 的标号。
int id[MAXN]; struct Tree { int l,r,val; }e[MAXN<<5];
- 建树
我们无法直接算出左右儿子编号,所以建树可以只用统计 数组。
inline void build(int &p,int l,int r) { p=++cnt; if(l==r) return; int mid=(l+r)>>1; build(t[p].l,l,mid); build(t[p].r,mid+1,r); return; }
表示动态开点的编号。
- 修改
与权值线段树唯一不同的是要再开一颗线段树。
inline int change(int p,int l,int r,int k) { int rt=++cnt; t[rt]=t[p],t[rt].val++; if(l==r) return rt; int mid=(l+r)>>1; if(k<=mid) t[rt].l=change(t[p].l,l,mid,k); else t[rt].r=change(t[p].r,mid+1,r,k-x); return rt; }
- 查询
由于每一颗线段树的结构相同,具有可加减性。
并且我们建立的是关于前缀的线段树,所以我们可以用前缀和的思想。
inline int ask(int l,int r,int a,int b,int k) { int ans=0,mid=(l+r)>>1; int x=t[t[b].l].val-t[t[a].l].val; if(l==r) return l; if(k<=x) ans=ask(l,mid,t[a].l,t[b].l,k); else ans=ask(mid+1,r,t[a].r,t[b].r,k); return ans; }
- 离散化
考虑到空间问题,我们需要离散化。
inline void dist() { sort(b+1,b+n+1); m=unique(b+1,b+n+1)-b-1; build(id[0],1,m); for(int i=1;i<=n;i++) { int p=lower_bound(b+1,b+m+1,a[i])-b; id[i]=change(id[i-1],1,m,p); } return; }
到这里,主席树的模板就没了,但还有一些操作需要自己去探索。
例题:
- P3919 【模板】可持久化线段树1
- P3834 【模板】可持久化线段树2
- P3567 [POI2014]KUR-Couriers
四.线段树合并
思想
前置知识:动态开点线段树、权值线段树。
顾名思义,就是建立一颗新的线段树,保存原有两颗线段树的信息。
这个思想比较简单,假设我们现在合并到了两棵线段树 的 位置,那么:
1.如果 有 位置, 没有,那么新的线段树 位置赋成 ,返回;
2.如果 有 位置, 没有,赋成 ,返回;
3.如果此时已经合并到两棵线段树的叶子节点了,就把 在 的值加到 上,把新线段树上的 位置赋成 ,返回;
4.递归处理左子树,递归处理右子树;
5.用左右子树的值更新当前节点;
6.将新线段树上的 位置赋成 ,返回;
处理方法
- 合并
线段树合并的核心操作。
inline int merge(int a,int b,int l,int r) { if(!a) return a; if(!b) return b; if(l==r) return a; int mid=(l+r)>>1; t[a].l=merge(t[a].l,t[b].l,l,mid); t[a].r=merge(t[a].r,t[b].r,mid+1,r); pushup(a); return a; }
假设要插入的点数为 ,那么时空复杂度均为
例题
P4556 【模板】线段树合并
首先有个暴力,利用树上差分的思想,把 上放一个数字改成 到 放一个数字, 到 放一个数字, 的 到 撤回一个数字, 的 的 到 撤回一个数字这四个操作。
接下来我们每个节点上开一个 ,表示 这个数字出现了多少次,那么节点 上出现最多的数字就是 数组的最大值。
那么我们求 节点的 数组可以暴力的把它的所有孩子的 数组按位相加起来来进行求解,然后如果这个节点上有插入或者删除数字的操作我们再对 数组进行几次操作就行了。
然后考虑优化。
每个点开一颗权值线段树表示这个点上有什么数字,每个数字出现了几次,然后求点 的权值线段树,就是将它所有孩子的线段树合并到一起,然后再对合并出来的线段树进行一些在这个节点的插入和删除操作。
(不愧是紫牌题)。
点击查看代码
P3521 [POI2011] ROT-Tree Rotations
#include<bits/stdc++.h> using namespace std; const int MAXN=1e5+5; int n,m; struct edge { int to,nxt; }e[MAXN<<1]; int head[MAXN],cnt; inline void add(int x,int y) { e[++cnt].to=y; e[cnt].nxt=head[x]; head[x]=cnt; return; } int dep[MAXN],fa[MAXN],hson[MAXN],siz[MAXN]; inline void dfs1(int x,int f) { dep[x]=dep[f]+1; fa[x]=f,siz[x]=1; int maxson=-1; for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to; if(y==f) continue; dfs1(y,x); siz[x]+=siz[y]; if(maxson<siz[y]) { maxson=siz[y]; hson[x]=y; } } return; } int top[MAXN]; inline void dfs2(int x,int ltop) { top[x]=ltop; if(!hson[x]) return; dfs2(hson[x],ltop); for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to; if(y==fa[x] || y==hson[x]) continue; dfs2(y,y); } return; } inline int LCA(int x,int y) { while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]]) swap(x,y); x=fa[top[x]]; } if(dep[x]<dep[y]) return x; return y; } struct Tree { int sum,num; int l,r; }t[MAXN*60]; int tot; int rt[MAXN]; inline void pushup(int p) { if(t[t[p].l].sum>t[t[p].r].sum || (t[t[p].l].num<t[t[p].r].num && t[t[p].l].sum==t[t[p].r].sum)) t[p].sum=t[t[p].l].sum,t[p].num=t[t[p].l].num; else t[p].sum=t[t[p].r].sum,t[p].num=t[t[p].r].num; return; } inline void change(int &p,int l,int r,int x,int k) { if(!p) p=++tot; if(l==r) {t[p].num=x,t[p].sum+=k;return;} int mid=(l+r)>>1; if(x<=mid) change(t[p].l,l,mid,x,k); else change(t[p].r,mid+1,r,x,k); pushup(p); return; } inline int merge(int a,int b,int l,int r) { if(!a) return b; if(!b) return a; if(l==r) {t[a].sum+=t[b].sum;return a;} int mid=(l+r)>>1; t[a].l=merge(t[a].l,t[b].l,l,mid); t[a].r=merge(t[a].r,t[b].r,mid+1,r); pushup(a); return a; } int ans[MAXN]; inline void dfs(int x,int f) { for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to; if(y==f) continue; dfs(y,x); rt[x]=merge(rt[x],rt[y],1,MAXN); } ans[x]=t[rt[x]].num; return; } int main() { ios_base::sync_with_stdio(false);cin.tie(0); cin>>n>>m; for(int i=1;i<=n-1;i++) { int x,y; cin>>x>>y; add(x,y),add(y,x); } dfs1(1,0);dfs2(1,1); for(int i=1;i<=m;i++) { int x,y,z; cin>>x>>y>>z; int lca=LCA(x,y); change(rt[x],1,MAXN,z,1); change(rt[y],1,MAXN,z,1); change(rt[lca],1,MAXN,z,-1); change(rt[fa[lca]],1,MAXN,z,-1); } dfs(1,0); for(int i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; }
P3605 [USACO17JAN] Promotion Counting P
这道题比较裸,直接权值线段树套线段树合并,查询就直接查当前节点的线段树,值域比较大,要离散化一下。
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=1e5+5; struct edge { int to,nxt; }e[MAXN<<1]; int head[MAXN],cnt; inline void add(int x,int y) { e[++cnt].to=y; e[cnt].nxt=head[x]; head[x]=cnt; return; } int n,m,a[MAXN],b[MAXN]; int rt[MAXN],ans[MAXN]; struct Tree { int cnt,l,r; }t[MAXN<<5]; int dat[MAXN<<5]; inline void pushup(int p) { t[p].cnt=t[t[p].l].cnt+t[t[p].r].cnt; return; } int tot; inline void change(int &p,int l,int r,int k) { if(!p) p=++tot; if(l==r) {t[p].cnt++,dat[p]=k;return;} int mid=(l+r)>>1; if(k<=mid) change(t[p].l,l,mid,k); else change(t[p].r,mid+1,r,k); pushup(p); return; } inline int merge(int a,int b,int l,int r) { if(!a) return b; if(!b) return a; if(l==r) {t[a].cnt+=t[b].cnt,dat[a]=dat[b];return a;} int mid=(l+r)>>1; t[a].l=merge(t[a].l,t[b].l,l,mid); t[a].r=merge(t[a].r,t[b].r,mid+1,r); pushup(a); return a; } inline int ask(int p,int l,int r,int a,int b) { if(l>b || r<a) return 0; if(l>=a && r<=b) return t[p].cnt; int mid=(l+r)>>1,ans=0; if(a<=mid) ans+=ask(t[p].l,l,mid,a,b); if(b>mid) ans+=ask(t[p].r,mid+1,r,a,b); return ans; } inline void dfs(int x,int fa) { for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to; if(y==fa) continue; dfs(y,x); rt[x]=merge(rt[x],rt[y],1,m); } ans[x]=ask(rt[x],1,m,a[x]+1,m); return; } int main() { ios_base::sync_with_stdio(false);cin.tie(0); cin>>n; for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i]; sort(b+1,b+n+1); m=unique(b+1,b+n+1)-b-1; for(int i=1;i<=n;i++) { a[i]=lower_bound(b+1,b+n+1,a[i])-b; change(rt[i],1,m,a[i]); } for(int i=2;i<=n;i++) { int x; cin>>x; add(x,i),add(i,x); } dfs(1,0); for(int i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; }
本文作者:Code_AC
本文链接:https://www.cnblogs.com/code-ac/p/17618085.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步