長鏈剖分
阿新 • • 發佈:2020-08-13
概念
長鏈剖分按深度剖分,重兒子為葉子最深的兒子。具體應用時可以通過指標記錄資訊,也可以優先遍歷重兒子來 \(dfs\),鏈上資訊為一個在 \(dfs\) 序上的連續區間,便於統計資訊和將資訊從重兒子合併過來。
void dfs_son(int x,int fa) { d[x]=dep[x]=d[fa]+1; for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to; if(y==fa) continue; dfs_son(y,x),dep[x]=max(dep[x],dep[y]); if(dep[y]>dep[son[x]]) son[x]=y; } len[x]=dep[x]-d[x]; }
一個節點到根節點的輕邊個數是 \(\sqrt n\) 級別的。
樹上 k 級祖先
可以做到 \(O(1)\) 查詢樹上 \(k\) 級祖先。對於每個長度為 \(len\) 的鏈,預處理鏈頂端向上 \(len\) 個節點,和沿鏈向下 \(len\) 個節點,其為即鏈上的點。詢問時先樹上倍增,跳到 \(2^i\) 級祖先,滿足 \(2^i \leqslant k <2^{i+1}\),對於 \(2^i\) 級祖先所在的鏈,滿足 \(k-2^i < 2^i < len\),所以可以跳到所在的鏈頂端後再通過預處理的資訊來調整。
void dfs_son(int x) { de[x]=dep[x]=de[f[x][0]]+1; for(int i=1;i<=19;++i) f[x][i]=f[f[x][i-1]][i-1]; for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to; dfs_son(y),dep[x]=max(dep[x],dep[y]); if(dep[y]>dep[son[x]]) son[x]=y; } } void dfs_dfn(int x,int tp,int anc) { top[x]=tp,dfn[x]=++cnt,u[cnt]=anc,d[cnt]=x; if(son[x]) dfs_dfn(son[x],tp,f[anc][0]); for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to; if(y==son[x]) continue; dfs_dfn(y,y,y); } } int ask(int x,int k) { if(!k) return x; x=f[x][lg[k]],k-=(1<<lg[k])+de[x]-de[top[x]],x=top[x]; if(k>=0) return u[dfn[x]+k]; else return d[dfn[x]-k]; }
優化 DP
對於形如狀態為 \(f_{x,i}\) ,\(i\) 那一維是關於深度的樹形 \(DP\),可以通過長鏈剖分來優化。每次轉移時先從重兒子來繼承,對輕兒子掃一遍其所在的鏈,因為每個輕兒子都是其所在鏈的頂端,所以每個點只會被掃一遍。