[整理]支 配 树

感觉天天做题智商并没有提高,于是颓废之余瞎学点东西。
不保证对除我自己之外的任何人易懂。

0.一些定义

为了方便叙述,以下涉及到节点大小比较的地方无特殊说明则直接用节点编号代替 ( ext{dfn})(DFS 树上的时间戳)。
在一个从给定起点出发能到达任意一个点的有向图上,一个点 (i) 支配(j) 当且仅当删去 (i) 后不存在起点到 (j) 的路径,显然一个点的支配点不一定只有一个。
方便起见,在 DFS 树上我们采用 Tarjan 的一些表示方法:
(u o v) 表示 (u) 直接连到 (v)
(uxrightarrow{.}v) 表示 (u)(v) 的祖先;
(uxrightarrow{+}v) 表示 (uxrightarrow{.}vland u e v)
以下罗列的一些引理或定理一般都可以直接一步反证出来,所以略去了绝大部分证明,如果难以理解可以自己画画图(我就是这么做的)。
另外,无特殊说明时默认节点不是起点。
定义节点 (u)直接支配点 ( ext{idom}_u) 为支配 (u) 的点中 ( ext{dfn}) 最大的点。
引理一:根据定义显然 ( ext{idom}_uxrightarrow{+}u)
定义节点 (u)半支配点 ( ext{sdom}_u) 为最小的节点 (v) 满足原图中存在一条 (v)(u) 的路径,路径上每个点(除 (v))都大于 (u)
引理二:显然 ( ext{sdom}_uxrightarrow{+}u)

1.一些有用的结论

引理三:显然 ( ext{idom}_uxrightarrow{.} ext{sdom}_u)
引理四:(forall uxrightarrow{.}v,uxrightarrow{.} ext{idom}_vlor ext{idom}_vxrightarrow{.} ext{idom}_u)
有了这些引理,我们就可以导出一些有用的定理。
定理一:(forall v ext{ s.t. } ext{sdom}_uxrightarrow{+}vxrightarrow{.}u, ext{sdom}_vge ext{sdom}_u),则 ( ext{idom}_u= ext{sdom}_v)
可以性感地理解为所有 (v) 都被封闭在 ( ext{sdom}_u)(u) 里面了,找不出一个 (v) 使得删去 ( ext{sdom}_u) 后还能从上面走到 (v),也就是 ( ext{sdom}_u) 支配 (u)
定理二:(exists v ext{ s.t. } ext{sdom}_uxrightarrow{+}vxrightarrow{.}u, ext{sdom}_vle ext{sdom}_u),则 ( ext{sdom}) 最小的点 (p) 满足 ( ext{idom}_p= ext{idom}_u)
显然 ( ext{idom}_u) 一定在 ( ext{sdom}_p) 上面,而上面最大的点就是 ( ext{idom}_p)
然后我们把上面两个揉起来就得到推论:
推论一:设满足 ( ext{sdom}_uxrightarrow{+}vxrightarrow{.}u) 的点中 ( ext{sdom}) 最小的 (v)(p),那么有

[ ext{idom}_u=egin{cases} ext{sdom}_p& ext{sdom}_u= ext{sdom}_p,\ ext{idom}_p& ext{otherwise.}end{cases} ]

所以 ( ext{idom}) 可以通过 ( ext{sdom}) 求出来,那么 ( ext{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 ightsquigarrow u o v ightsquigarrow 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 的算法,我为了练习选择了后者。然后就因为计算 ( ext{idom}) 时写反 (u,v) 调了一下午。

内容来自_ajthreac_的博客(https://www.cnblogs.com/juruoajh/),未经允许,不得转载。
原文地址:https://www.cnblogs.com/juruoajh/p/15240363.html