Preface

给定一个有向图和一个起点【学习小记】支配树【图论】_支配树,我们需要知道起点到某个点的关于必经点的信息。
若起点到点v的所有路径均经过点u,则我们称点u支配点v,显然一个点支配自己本身

顾名思义,支配树就是由某些支配关系构成的树。

定义

约定一些记号
【学习小记】支配树【图论】_支配树_02,表示一条从u到v的有向边
【学习小记】支配树【图论】_带权并查集_03,表示【学习小记】支配树【图论】_带权并查集_04在DFS树上的父亲。
【学习小记】支配树【图论】_带权并查集_05为从【学习小记】支配树【图论】_支配树开始DFS整个图,【学习小记】支配树【图论】_有向图_07号点的【学习小记】支配树【图论】_支配树_08时间戳。
【学习小记】支配树【图论】_支配树_09,表示DFS树上【学习小记】支配树【图论】_带权并查集_04【学习小记】支配树【图论】_点集_11的路径上的点集
【学习小记】支配树【图论】_带权并查集_12表示路径上点集中除去点【学习小记】支配树【图论】_带权并查集_04剩余的集合

我们定义一个点【学习小记】支配树【图论】_带权并查集_04的最近支配点【学习小记】支配树【图论】_带权并查集_15,它支配【学习小记】支配树【图论】_带权并查集_04,且它也被除u以外的所有支配u的点支配

性质


显然【学习小记】支配树【图论】_带权并查集_15唯一存在,且若我们重新建一个图,所有的【学习小记】支配树【图论】_带权并查集_15【学习小记】支配树【图论】_带权并查集_04连一条边,那么这个新图是一棵以【学习小记】支配树【图论】_支配树为根的树,也就是说【学习小记】支配树【图论】_点集_21不会成环(可以用反证法简单证明)。
这棵树就是我们说的支配树。


【学习小记】支配树【图论】_带权并查集_15一定是【学习小记】支配树【图论】_带权并查集_04【学习小记】支配树【图论】_支配树_08树上的祖先。(一样可以反证法简单证明)


问题就是要求出【学习小记】支配树【图论】_点集_21

求解

为了求出【学习小记】支配树【图论】_点集_21,我们引入半必经点的概念。

半必经点

回归tarjan求强连通分量的算法,我们来观察DFS过程中会出现哪些边。

  • 树边,DFS树上的父亲——儿子边。
  • 后向边,祖先向后代连的非树边的边。
  • 返祖边,DFS序大的后代向DFS序小的祖先连的边
  • 横叉边,DFS序大的点向DFS序小的点连的边,且没有祖先后代关系。

容易证明不存在上面四种边以外的边。

【学习小记】支配树【图论】_带权并查集_27表示从【学习小记】支配树【图论】_带权并查集_04开始,沿着边反着走,能够中途不经过u的祖先,最后到达的深度最浅(【学习小记】支配树【图论】_支配树_29最小)的【学习小记】支配树【图论】_带权并查集_04的祖先。

形式化的,【学习小记】支配树【图论】_带权并查集_27【学习小记】支配树【图论】_带权并查集_04的祖先,且存在原图中的一条路径【学习小记】支配树【图论】_支配树_33【学习小记】支配树【图论】_有向图_34

可以发现这两种定义是等价的,因为不可能通过反向边走到DFS序更小的且不是祖先的点去。

我们考虑它可能怎么走,要么通过树边/后向边的反向边直接走到一个祖先,要么通过返祖边/横叉边的反向边走到某些【学习小记】支配树【图论】_支配树_29更大的点【学习小记】支配树【图论】_支配树_36,然后在【学习小记】支配树【图论】_有向图_37上找一个【学习小记】支配树【图论】_支配树_38,满足【学习小记】支配树【图论】_支配树_39,所有的【学习小记】支配树【图论】_有向图_40的最浅的那个就用来更新【学习小记】支配树【图论】_带权并查集_27

可以发现最浅的【学习小记】支配树【图论】_有向图_40一定是【学习小记】支配树【图论】_带权并查集_04的祖先,因此我们不必担心不合法的问题。

那么我们就得到了【学习小记】支配树【图论】_支配树_44的求法,按照DFS序倒序枚举所有点【学习小记】支配树【图论】_带权并查集_04,按照上面的做法来不断更新,查询祖先可以用带权并查集来做,求出一个点的【学习小记】支配树【图论】_支配树_44之后将它的所有树边儿子与它合并即可。

问题在于求【学习小记】支配树【图论】_点集_21
我们考虑【学习小记】支配树【图论】_有向图_48,找到【学习小记】支配树【图论】_支配树_49最浅的一个。

有定理:若【学习小记】支配树【图论】_点集_50,则【学习小记】支配树【图论】_支配树_51,否则【学习小记】支配树【图论】_带权并查集_52
这个定理请自行理解…我也不会证

有了这个定理之后我们可以将【学习小记】支配树【图论】_带权并查集_04挂在【学习小记】支配树【图论】_带权并查集_27上,扫到【学习小记】支配树【图论】_带权并查集_27的时候就找到【学习小记】支配树【图论】_带权并查集_04相应的【学习小记】支配树【图论】_点集_11,若【学习小记】支配树【图论】_有向图_58就直接【学习小记】支配树【图论】_支配树_51,否则因为此时【学习小记】支配树【图论】_支配树_60还没有求出来,我们可以先将【学习小记】支配树【图论】_带权并查集_15指向【学习小记】支配树【图论】_点集_11,最后全部都做完以后按照DFS序正序扫一遍将这部分的【学习小记】支配树【图论】_带权并查集_15改成【学习小记】支配树【图论】_带权并查集_64即可。

时间复杂度【学习小记】支配树【图论】_支配树_65(因为不能按秩合并)

Code

​洛谷P5180​​,支配树模板题。

#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 200005
#define M 300005
using namespace std;
vector<int> pre[N];
int fs[N],nt[M],dt[M],pr[M],n,m,m1;

int semi[N],idom[N],ft[N],fp[N],dfn[N],dfw[N],fa[N];
vector<int> ids[N];
int gmin(int x,int y)
{
return (!y||(dfn[x]<dfn[y]&&x))?x:y;
}
void link(int x,int y)
{
nt[++m1]=fs[x];
dt[fs[x]=m1]=y;
}
void dfs(int k)
{
dfw[dfn[k]=++dfn[0]]=k;
for(int i=fs[k];i;i=nt[i])
{
int p=dt[i];
if(!dfn[p]) fa[p]=k,dfs(p);
if(dfn[k]<dfn[p]) semi[p]=gmin(semi[p],k);
else pre[p].push_back(k);
}
}
int getf(int k)
{
if(!ft[k]||ft[k]==k) return k;
int p=getf(ft[k]);
fp[k]=(dfn[semi[fp[k]]]<dfn[semi[fp[ft[k]]]])?fp[k]:fp[ft[k]];
return ft[k]=p;
}
int fd(int k)
{
getf(k);return fp[k];
}
void merge(int x,int y)
{
int fx=getf(x),fy=getf(y);
ft[fy]=fx,fp[fy]=(dfn[semi[fp[fy]]]<dfn[semi[fp[fx]]])?fp[fy]:fp[fx];
}
void make()
{
fod(i,n,1)
{
int k=dfw[i];
if(i>1)
{
for(int u:pre[k])
{
int v=fd(u);
semi[k]=gmin(semi[k],semi[v]);
}
ids[semi[k]].push_back(k);
}
for(int u:ids[k])
{
int v=fd(u);
idom[u]=(semi[v]==k)?k:v;
}
fp[k]=k;
for(int i=fs[k];i;i=nt[i]) if(fa[dt[i]]==k) merge(k,dt[i]);
}
for(int i=2,k=dfw[2];i<=n;++i,k=dfw[i]) if(idom[k]!=semi[k]) idom[k]=idom[idom[k]];
}
int sz[N];
int main()
{
cin>>n>>m;
fo(i,1,m)
{
int x,y;
scanf("%d%d",&x,&y);
link(x,y),pre[y].push_back(x);
}
dfs(1);
make();
fod(i,n,1)
{
sz[dfw[i]]++;
sz[idom[dfw[i]]]+=sz[dfw[i]];
}
fo(i,1,n) printf("%d ",sz[i]);
}