这里会显示出您选择的修订版和当前版本之间的差别。
| 后一修订版 | 前一修订版 | ||
|
2020-2021:teams:legal_string:树链剖分_lgwza [2020/07/07 15:15] lgwza 创建 |
2020-2021:teams:legal_string:树链剖分_lgwza [2020/08/04 22:21] (当前版本) lgwza [例题] |
||
|---|---|---|---|
| 行 71: | 行 71: | ||
| \end{array}\end{array} | \end{array}\end{array} | ||
| $$ 以下为代码实现。 | $$ 以下为代码实现。 | ||
| + | |||
| + | 我们先给出一些定义: | ||
| + | |||
| + | * $fa(x)$ 表示结点 $x$ 在树上的父亲。 | ||
| + | * $dep(x)$ 表示结点 $x$ 在树上的深度。 | ||
| + | * $siz(x)$ 表示结点 $x$ 的子树的结点个数。 | ||
| + | * $son(x)$ 表示结点 $x$ 的**重儿子**。 | ||
| + | * $top(x)$ 表示结点 $x$ 所在**重链**的顶部结点(深度最小)。 | ||
| + | * $dfn(x)$ 表示结点 $x$ 的**DFS序**,也是其在线段树中的编号。 | ||
| + | * $rnk(x)$ 表示 DFS 序所对应的结点编号,有 $rnk(dfn(x))=x$ 。 | ||
| + | |||
| + | 我们进行两遍 DFS 预处理出这些值,其中第一次 DFS 求出 $fa(x),dep(x),siz(x),son(x)$,第二次 DFS 求出 $top(x),dfn(x),rnk(x)$。 | ||
| + | |||
| + | <code cpp> | ||
| + | void dfs1(int o){ | ||
| + | son[o]=-1; | ||
| + | siz[o]=1; | ||
| + | for(int j=h[o];j;j=nxt[j]){ | ||
| + | if(!dep[p[j]]){ | ||
| + | dep[p[j]]=dep[o]+1; | ||
| + | fa[p[j]]=o; | ||
| + | dfs1(p[j]); | ||
| + | siz[o]+=siz[p[j]]; | ||
| + | if(son[o]==-1||siz[p[j]]>siz[son[o]]) son[o]=p[j]; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | void dfs2(int o,int t){ | ||
| + | top[o]=t; | ||
| + | cnt++; | ||
| + | dfn[o]=cnt; | ||
| + | rnk[cnt]=o; | ||
| + | if(son[o]==-1) return; | ||
| + | dfs2(son[o],t);// 优先对重儿子进行 DFS ,可以保证同一条重链上的点 DFS 序连续 | ||
| + | for(int j=h[o];j;j=nxt[j]){ | ||
| + | if(p[j]!=son[o]&&p[j]!=fa[o]) dfs2(p[j],p[j]); | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | ===== 重链剖分的性质 ===== | ||
| + | |||
| + | **树上每个结点都属于且仅属于一条重链**。 | ||
| + | |||
| + | 重链开头的结点不一定是重子结点(因为重边是对于每一个结点都有定义的)。 | ||
| + | |||
| + | 所有的重链将整棵树**完全剖分**。 | ||
| + | |||
| + | 在剖分时**优先遍历重儿子**,最后重链的 DFS 序就会是连续的。 | ||
| + | |||
| + | 在剖分时**重边优先遍历**,最后树的 DFN 序上,重链内的 DFN 序是连续的。按 DFN 排序后的序列即为剖分后的链。 | ||
| + | |||
| + | 一棵子树内的 DFN 序是连续的。 | ||
| + | |||
| + | 可以发现,当我们向下经过一条轻边时,所在子树的大小至少会除以二。 | ||
| + | |||
| + | 因此,对于树上的任意一条路径,把它拆分成从 LCA 分别向两边往下走,分别最多走 $O(\log n)$次,因此,树上的每条路径都可以被拆分成不超过 $O(\log n)$条重链。 | ||
| + | |||
| + | ===== 常见应用 ===== | ||
| + | |||
| + | ==== 路径上维护 ==== | ||
| + | |||
| + | 用树链剖分求树上两点路径权值和,伪代码如下: $$ | ||
| + | \begin{array}{l}\text{TREE-PATH-SUM }(u,v)\\ | ||
| + | \begin{array}{ll} | ||
| + | 1 & tot\gets 0 \\ | ||
| + | 2 & \textbf{while }u.top\text{ is not }v.top\\ | ||
| + | 3 & \qquad\textbf{if }u.top.deep<v.top.deep\\ | ||
| + | 4& \qquad\qquad\text{SWAP}(u,v)\\ | ||
| + | 5 & \qquad tot\gets tot+\text{sum of values between }u\text{ and }u.top\\ | ||
| + | 6 & \qquad u \gets u.top.father\\ | ||
| + | 7 & tot\gets tot +\text{sum of values between }u\text{ and }v\\ | ||
| + | 8 & \textbf{return }tot | ||
| + | \end{array}\end{array} | ||
| + | $$ 链上的 DFS 序是连续的,可以使用线段树、树状数组维护。 | ||
| + | |||
| + | 每次选择深度较大的链往上跳,直到两点在同一条链上。 | ||
| + | |||
| + | 同样的跳链结构适用于维护、统计路径上的其他信息。 | ||
| + | |||
| + | ==== 子树维护 ==== | ||
| + | |||
| + | 有时会要求维护子树上的信息,譬如将以 $x$ 为根的子树的所有结点的权值增加 $v$。 | ||
| + | |||
| + | 在 DFS 搜索的时候,子树中的结点的 DFS 序是连续的。 | ||
| + | |||
| + | 每一个结点记录 bottom 表示所在子树连续区间末端的结点。 | ||
| + | |||
| + | 这样就把子树信息转化为连续的一段区间信息。 | ||
| + | |||
| + | ==== 求最近公共祖先 ==== | ||
| + | |||
| + | 不断向上跳重链,当跳到同一条重链上时,深度较小的结点即为 LCA。 | ||
| + | |||
| + | 向上跳重链时需要先跳所在重链顶端深度较大的那个。 | ||
| + | |||
| + | 参考代码: | ||
| + | |||
| + | <code cpp> | ||
| + | int lca(int u,int v){ | ||
| + | while(top[u]!=top[v]){ | ||
| + | if(dep[top[u]]>dep[top[v]]) | ||
| + | u=fa[top[u]]; | ||
| + | else | ||
| + | v=fa[top[v]]; | ||
| + | } | ||
| + | return dep[u]>dep[v]?v:u; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | ===== 例题 ===== | ||
| + | [[https://www.luogu.com.cn/problem/P3384|P3384 【模板】轻重链剖分]] | ||
| + | |||
| + | 参考代码: | ||
| + | |||
| + | <hidden> | ||
| + | <code cpp> | ||
| + | #include<bits/stdc++.h> | ||
| + | using namespace std; | ||
| + | const int N=1e5+5; | ||
| + | typedef long long ll; | ||
| + | |||
| + | int n,m,root,mod; | ||
| + | int cnt,head[N],nxt[N<<1],to[N<<1],w[N],nw[N]; | ||
| + | int tr[N<<2],add[N<<2]; | ||
| + | int hson[N],dfn[N],fa[N],tot,dep[N],siz[N],top[N],rk[N]; | ||
| + | void addedge(int u,int v){ | ||
| + | nxt[++cnt]=head[u]; | ||
| + | head[u]=cnt; | ||
| + | to[cnt]=v; | ||
| + | } | ||
| + | // segment tree | ||
| + | inline int ls(int o){ | ||
| + | return o<<1; | ||
| + | } | ||
| + | inline int rs(int o){ | ||
| + | return o<<1|1; | ||
| + | } | ||
| + | void push_up(int o){ | ||
| + | tr[o]=(tr[ls(o)]+tr[rs(o)])%mod; | ||
| + | } | ||
| + | void build(int o,int l,int r){ | ||
| + | if(l==r){ | ||
| + | tr[o]=nw[l]%mod; | ||
| + | return; | ||
| + | } | ||
| + | int mid=l+r>>1; | ||
| + | build(ls(o),l,mid); | ||
| + | build(rs(o),mid+1,r); | ||
| + | push_up(o); | ||
| + | } | ||
| + | void f(int o,int l,int r,int k){ | ||
| + | add[o]=(add[o]+k); | ||
| + | tr[o]=(tr[o]+(r-l+1)*k)%mod; | ||
| + | } | ||
| + | void push_down(int o,int l,int r){ | ||
| + | int mid=l+r>>1; | ||
| + | f(ls(o),l,mid,add[o]); | ||
| + | f(rs(o),mid+1,r,add[o]); | ||
| + | add[o]=0; | ||
| + | } | ||
| + | void xg(int o,int nl,int nr,int l,int r,int k){ | ||
| + | if(nl<=l&&r<=nr){ | ||
| + | add[o]=(add[o]+k)%mod; | ||
| + | tr[o]=(tr[o]+(r-l+1)*k)%mod; | ||
| + | return; | ||
| + | } | ||
| + | push_down(o,l,r); | ||
| + | int mid=l+r>>1; | ||
| + | if(nl<=mid) xg(ls(o),nl,nr,l,mid,k); | ||
| + | if(nr>mid) xg(rs(o),nl,nr,mid+1,r,k); | ||
| + | push_up(o); | ||
| + | } | ||
| + | int cx(int o,int nl,int nr,int l,int r){ | ||
| + | if(nl<=l&&r<=nr) return tr[o]%mod; | ||
| + | push_down(o,l,r); | ||
| + | int ret=0; | ||
| + | int mid=l+r>>1; | ||
| + | if(nl<=mid) ret+=cx(ls(o),nl,nr,l,mid); | ||
| + | if(nr>mid) ret+=cx(rs(o),nl,nr,mid+1,r); | ||
| + | return ret%mod; | ||
| + | } | ||
| + | // original tree | ||
| + | int cxRange(int x,int y){ | ||
| + | int ans=0; | ||
| + | while(top[x]!=top[y]){ | ||
| + | if(dep[top[x]]<dep[top[y]]) swap(x,y); | ||
| + | ans+=cx(1,dfn[top[x]],dfn[x],1,n); | ||
| + | ans%=mod; | ||
| + | x=fa[top[x]]; | ||
| + | } | ||
| + | if(dep[x]>dep[y]) swap(x,y); | ||
| + | ans+=cx(1,dfn[x],dfn[y],1,n); | ||
| + | ans%=mod; | ||
| + | return ans; | ||
| + | } | ||
| + | void xgRange(int x,int y,int k){ | ||
| + | k%=mod; | ||
| + | while(top[x]!=top[y]){ | ||
| + | if(dep[top[x]]<dep[top[y]]) swap(x,y); | ||
| + | xg(1,dfn[top[x]],dfn[x],1,n,k); | ||
| + | x=fa[top[x]]; | ||
| + | } | ||
| + | if(dep[x]>dep[y]) swap(x,y); | ||
| + | xg(1,dfn[x],dfn[y],1,n,k); | ||
| + | } | ||
| + | int cxSon(int x){ | ||
| + | return cx(1,dfn[x],dfn[x]+siz[x]-1,1,n); | ||
| + | } | ||
| + | void xgSon(int x,int k){ | ||
| + | xg(1,dfn[x],dfn[x]+siz[x]-1,1,n,k); | ||
| + | } | ||
| + | // dfs | ||
| + | void dfs1(int u,int f,int deep){ | ||
| + | dep[u]=deep; | ||
| + | siz[u]=1; | ||
| + | fa[u]=f; | ||
| + | for(int i=head[u];i;i=nxt[i]){ | ||
| + | int v=to[i]; | ||
| + | if(v==fa[u]) continue; | ||
| + | dfs1(v,u,deep+1); | ||
| + | siz[u]+=siz[v]; | ||
| + | if(hson[u]==0||siz[hson[u]]<siz[v]) hson[u]=v; | ||
| + | } | ||
| + | } | ||
| + | void dfs2(int u,int t){ | ||
| + | top[u]=t; | ||
| + | dfn[u]=++tot; | ||
| + | nw[tot]=w[u]; | ||
| + | rk[tot]=u; | ||
| + | if(!hson[u]) return; | ||
| + | dfs2(hson[u],t); | ||
| + | for(int i=head[u];i;i=nxt[i]){ | ||
| + | int v=to[i]; | ||
| + | if(v==fa[u]||v==hson[u]) continue; | ||
| + | dfs2(v,v); | ||
| + | } | ||
| + | } | ||
| + | int main(){ | ||
| + | scanf("%d %d %d %d",&n,&m,&root,&mod); | ||
| + | for(int i=1;i<=n;i++) scanf("%d",&w[i]); | ||
| + | for(int i=1;i<=n-1;i++){ | ||
| + | int x,y; | ||
| + | scanf("%d %d",&x,&y); | ||
| + | addedge(x,y); | ||
| + | addedge(y,x); | ||
| + | } | ||
| + | dfs1(root,0,1); | ||
| + | dfs2(root,root); | ||
| + | build(1,1,n); | ||
| + | // printf("fadfa\n"); | ||
| + | while(m--){ | ||
| + | int op; | ||
| + | scanf("%d",&op); | ||
| + | if(op==1){ | ||
| + | int x,y; | ||
| + | int k; | ||
| + | scanf("%d %d %d",&x,&y,&k); | ||
| + | k%=mod; | ||
| + | xgRange(x,y,k); | ||
| + | } | ||
| + | else if(op==2){ | ||
| + | int x,y; | ||
| + | scanf("%d %d",&x,&y); | ||
| + | printf("%d\n",cxRange(x,y)); | ||
| + | } | ||
| + | else if(op==3){ | ||
| + | int x; | ||
| + | int k; | ||
| + | scanf("%d %d",&x,&k); | ||
| + | k%=mod; | ||
| + | xgSon(x,k); | ||
| + | } | ||
| + | else if(op==4){ | ||
| + | int x; | ||
| + | scanf("%d",&x); | ||
| + | printf("%d\n",cxSon(x)); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | </hidden> | ||
| + | |||
| + | ===== 参考资料 ===== | ||
| + | [[https://oi-wiki.org/graph/hld/|OI Wiki]] | ||