[整理]支 配 樹
感覺天天做題智商並沒有提高,於是頹廢之餘瞎學點東西。
不保證對除我自己之外的任何人易懂。
0.一些定義
為了方便敘述,以下涉及到節點大小比較的地方無特殊說明則直接用節點編號代替 \(\text{dfn}\)(DFS 樹上的時間戳)。
在一個從給定起點出發能到達任意一個點的有向圖上,一個點 \(i\) 支配點 \(j\) 當且僅當刪去 \(i\) 後不存在起點到 \(j\) 的路徑,顯然一個點的支配點不一定只有一個。
方便起見,在 DFS 樹上我們採用 Tarjan 的一些表示方法:
\(u\to v\) 表示 \(u\) 直接連到 \(v\);
\(u\xrightarrow{.}v\) 表示 \(u\)
\(u\xrightarrow{+}v\) 表示 \(u\xrightarrow{.}v\land u\ne v\)。
以下羅列的一些引理或定理一般都可以直接一步反證出來,所以略去了絕大部分證明,如果難以理解可以自己畫畫圖(我就是這麼做的)。
另外,無特殊說明時預設節點不是起點。
定義節點 \(u\) 的直接支配點 \(\text{idom}_u\) 為支配 \(u\) 的點中 \(\text{dfn}\) 最大的點。
引理一:根據定義顯然 \(\text{idom}_u\xrightarrow{+}u\)。
定義節點 \(u\) 的半支配點 \(\text{sdom}_u\)
引理二:顯然 \(\text{sdom}_u\xrightarrow{+}u\)。
1.一些有用的結論
引理三:顯然 \(\text{idom}_u\xrightarrow{.}\text{sdom}_u\)。
引理四:\(\forall u\xrightarrow{.}v,u\xrightarrow{.}\text{idom}_v\lor\text{idom}_v\xrightarrow{.}\text{idom}_u\)。
有了這些引理,我們就可以匯出一些有用的定理。
定理一:
可以性感地理解為所有 \(v\) 都被封閉在 \(\text{sdom}_u\) 到 \(u\) 裡面了,找不出一個 \(v\) 使得刪去 \(\text{sdom}_u\) 後還能從上面走到 \(v\),也就是 \(\text{sdom}_u\) 支配 \(u\)。
定理二:若\(\exists v\text{ s.t. }\text{sdom}_u\xrightarrow{+}v\xrightarrow{.}u,\text{sdom}_v\le\text{sdom}_u\),則 \(\text{sdom}\) 最小的點 \(p\) 滿足 \(\text{idom}_p=\text{idom}_u\)。
顯然 \(\text{idom}_u\) 一定在 \(\text{sdom}_p\) 上面,而上面最大的點就是 \(\text{idom}_p\)。
然後我們把上面兩個揉起來就得到推論:
推論一:設滿足 \(\text{sdom}_u\xrightarrow{+}v\xrightarrow{.}u\) 的點中 \(\text{sdom}\) 最小的 \(v\) 為 \(p\),那麼有 \[\text{idom}_u=\begin{cases}\text{sdom}_p&\text{sdom}_u=\text{sdom}_p,\\\text{idom}_p&\text{otherwise.}\end{cases} \]
所以 \(\text{idom}\) 可以通過 \(\text{sdom}\) 求出來,那麼 \(\text{sdom}\) 怎麼求呢?我們有以下解法:
找到所有原圖中連向 \(u\) 的 \(v\),所有 \(<u\) 的 \(v\) 和 \(>u\) 的 \(v\) 的 \(>u\) 的祖先的半支配點都可能是 \(u\) 的半支配點(主語是 \(<u\) 的 \(v\) 和半支配點)。性感理解發現它顯然是充分的。
通過這些東西理論上就可以完整地求出直接支配點了,塔老師意料之中地又一次給出了一個神奇的實現:
對於半支配點,我們倒序列舉,由於需要考慮祖先,我們自然想到用並查集維護,帶上一個最小半支配點的權值。
然後我們建出半支配樹後按照推論一直接計算直接支配點即可,這一步可以利用求半支配點時留下的資訊。
2.程式碼實現
洛谷 P5180 【模板】支配樹
實現的時候需要注意一個細節:處理直接支配點時列舉完父親要清掉父親。
const int N=200010,M=300010;
int n,m;
struct Graph {
struct Edge {
int to,nxt;
}e[M];
int hd[N],cnt;
il void ade(int u,int v){
e[++cnt].to=v,e[cnt].nxt=hd[u],hd[u]=cnt;
}
Graph(){
memset(hd,0,sizeof hd),cnt=0;
}
}G,R,D;//原圖、反圖、半支配樹
int dfn[N],idx[N],fa[N],tim;
void DFS(int u,int ff){
idx[dfn[u]=++tim]=u,fa[u]=ff;
for(rg int i=G.hd[u];i;i=G.e[i].nxt){
int v=G.e[i].to;
if(!dfn[v])DFS(v,u);
}
}
//直接支配/半支配/父親/權值
int idm[N],sdm[N],f[N],mn[N];
int F(int k){//並查集維護最小sdom
if(f[k]==k)return k;
int ff=F(f[k]);
if(dfn[sdm[mn[f[k]]]]<dfn[sdm[mn[k]]])mn[k]=mn[f[k]];
return f[k]=ff;
}
void Tarjan(){
for(rg int i=1;i<=n;i++)f[i]=mn[i]=sdm[i]=i;
for(rg int i=tim;i>1;i--){
int u=idx[i];
for(rg int i=R.hd[u];i;i=R.e[i].nxt){
int v=R.e[i].to;
if(!dfn[v])continue;
F(v);
if(dfn[sdm[mn[v]]]<dfn[sdm[u]])sdm[u]=sdm[mn[v]];
}
D.ade(sdm[u],u),f[u]=fa[u];
for(rg int i=D.hd[fa[u]];i;i=D.e[i].nxt){
int v=D.e[i].to;F(v);//運用推論一求idom
idm[v]=sdm[mn[v]]==fa[u]?fa[u]:mn[v];
}
D.hd[fa[u]]=0;
}
for(rg int i=2;i<=tim;i++){
int u=idx[i];//完善推論一的第二種情況
if(idm[u]!=sdm[u])idm[u]=idm[idm[u]];
}
}
int siz[N];
int main(){
Read(n),Read(m);
for(rg int i=1,u,v;i<=m;i++){
Read(u),Read(v),G.ade(u,v),R.ade(v,u);
}
DFS(1,0),Tarjan();
for(rg int i=tim;i;i--){
int u=idx[i];//統計答案
siz[idm[u]]+=++siz[u];
}
for(rg int i=1;i<=n;i++)printf("%d ",siz[i]);
puts("");
KafuuChino HotoKokoa
}
3.簡單應用
省選聯考 2021 A 支配。
考慮加入 \((u,v)\) 之後滿足哪些條件的點會發生變化(顯然受支配集的變化一定是減少)。
發生變化也就是某個祖先不再支配它,而一個點的受支配集變化會引起整個子樹變化,所以我們只需要考慮父親不再支配它的點。
這樣的點 \(x\) 一定滿足 \(1\rightsquigarrow u\to v\rightsquigarrow x\) 且不經過 \(x\) 的父親。
顯然是不能每次暴力求的,我們可以 \(O(n^2)\) 對於每個點預處理出不經過它的父親就能到達它的所有點,複雜度已經足以通過本題。
struct DominatorTree {
struct Edge {
int to,nxt;
}e[N];
int hd[N],cnt;
il void ade(int u,int v){
e[++cnt].to=v,e[cnt].nxt=hd[u],hd[u]=cnt;
}
int dfn[N],tim,siz[N];
void DFS(int u,int ff){
dfn[u]=++tim,siz[u]=1;
for(rg int i=hd[u];i;i=e[i].nxt){
int v=e[i].to;
if(v!=ff)DFS(v,u),siz[u]+=siz[v];
}
}
}T;
bool vis[N];
void DFSForbid(int u,int ban){
vis[u]=1;
for(rg int i=R.hd[u];i;i=R.e[i].nxt){
int v=R.e[i].to;
if(!vis[v]&&v!=ban)DFSForbid(v,ban);
}
}
bool ok[N][N];//whether i can reach j without Xing fa[j]
void Init(){
for(rg int i=2;i<=n;i++){
memset(vis,0,sizeof vis);
DFSForbid(i,idm[i]);
for(rg int j=1;j<=n;j++)ok[j][i]=vis[j];
}
}
bool isf[N];int d[N];
int Query(int u,int v){
memset(isf,0,sizeof isf),memset(d,0,sizeof d);
for(rg int i=u;i;i=idm[i])isf[i]=1;
for(rg int i=2;i<=n;i++){
if(!isf[idm[i]]&&ok[v][i]){
d[T.dfn[i]]++,d[T.dfn[i]+T.siz[i]]--;
}
}
int res=0;
for(rg int i=1;i<=n;i++)res+=(d[i]+=d[i-1])!=0;
return res;
}
int main(){
Read(n),Read(m),Read(q);
for(rg int i=1,u,v;i<=m;i++){
Read(u),Read(v),G.ade(u,v),R.ade(v,u);
}
Tarjan();
for(rg int i=2;i<=n;i++)T.ade(idm[i],i);
Init(),T.DFS(1,0);
for(rg int i=1,u,v;i<=q;i++){
Read(u),Read(v);
cout<<Query(u,v)<<endl;
}
KafuuChino HotoKokoa
}
可以使用 \(O(n^2)\) 暴力求支配樹也可以使用 Tarjan 的演算法,我為了練習選擇了後者。然後就因為計算 \(\text{idom}\) 時寫反 \(u,v\) 調了一下午。