1. 程式人生 > 其它 >[整理]支 配 樹

[整理]支 配 樹

感覺天天做題智商並沒有提高,於是頹廢之餘瞎學點東西。
不保證對除我自己之外的任何人易懂。

0.一些定義

為了方便敘述,以下涉及到節點大小比較的地方無特殊說明則直接用節點編號代替 \(\text{dfn}\)(DFS 樹上的時間戳)。
在一個從給定起點出發能到達任意一個點的有向圖上,一個點 \(i\) 支配\(j\) 當且僅當刪去 \(i\) 後不存在起點到 \(j\) 的路徑,顯然一個點的支配點不一定只有一個。
方便起見,在 DFS 樹上我們採用 Tarjan 的一些表示方法:
\(u\to v\) 表示 \(u\) 直接連到 \(v\)
\(u\xrightarrow{.}v\) 表示 \(u\)

\(v\) 的祖先;
\(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\)
為最小的節點 \(v\) 滿足原圖中存在一條 \(v\)\(u\) 的路徑,路徑上每個點(除 \(v\))都大於 \(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\)
有了這些引理,我們就可以匯出一些有用的定理。
定理一:

\(\forall v\text{ s.t. }\text{sdom}_u\xrightarrow{+}v\xrightarrow{.}u,\text{sdom}_v\ge\text{sdom}_u\),則 \(\text{idom}_u=\text{sdom}_v\)
可以性感地理解為所有 \(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\) 調了一下午。

內容來自_ajthreac_的部落格(https://www.cnblogs.com/juruoajh/),未經允許,不得轉載。