什么是支配树?
对于一张有向图,确定一个根,如果根到 x 的每条路径都经过 y,那么称 y 是 x 支配点。求出原图的一个 dfs 树,那么 x 的支配点一定在 x 到根的链上。如果每个点向自己深度最深的支配点连边,就构成了支配树。
明确一些有向图 dfs 树的性质:
1.树边总是由 dfn 小的点指向 dfn 大的点,非树边(两端点无祖先关系)总是由 dfn 大的点指向 dfn 小的点。
2.若 v,w 是图中节点且 dfn[v]<=dfn[w],则任意从 v 到 w 的路径必然包含它们在 dfs 树中的一个公共祖先。
如果存在 y 到 x 的一条路径,使得路径上的点(除 x,y)dfn 都大于 x,那么称 y 是 x 的一个半支配点。可以证明 x 的半支配点一定是 x 的祖先,我们记 sdom[x] 为 x 半支配点中深度最小的点。
求 sdom:考虑枚举路径上最后一个点 z, s d o m [ x ] = min { s d o m [ z ] ∣ ( z , x ) ∈ E , d f n [ z ] > d f n [ x ] } sdom[x]=\min\{sdom[z]|(z,x)\in E,dfn[z]>dfn[x]\} sdom[x]=min{sdom[z]∣(z,x)∈E,dfn[z]>dfn[x]}。
怎么求支配点?对于一个点 x,sdom[x] 到 x 之间的点一定不会是支配点。对于 x 到根的每个点 y,把不合法的点删掉后剩下的深度最深的点就是支配点。
实现:
1.求 sdom:相当于求 x 到根的路径上 dfn 大于 dfn[x] 的点的最小值。按 dfn 从大到小处理,带权并查集维护子树。
2.求 idom:相当于求 x 到根的路径上深度大于 sdom[x] 的点的最小值。同样按 sdom[x] 从大到小维护带权并查集。注意桶排。
(可能)比较好写的做法:求完 sdom 以后 sdom[x] 向 x 连边。把图变为 DAG.
洛谷模板:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=200010,M=300010;
struct edge{
int to,next;
}ed[M];
int sz,head[N],dfn[N],deep[N],fa[N],size[N],a[N],c[N],tim,sdom[N],idom[N],wb[N],lk[N],tr[M];
vector <int> b[N],d[N];
void add_edge(int from,int to)
{
ed[++sz].to=to;
ed[sz].next=head[from];
head[from]=sz;
}
int read()
{
int x=0;char c=getchar(),flag='+';
while(!isdigit(c)) flag=c,c=getchar();
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return flag=='-'?-x:x;
}
inline int chk(int x,int y)
{
if(!x||!y) return x|y;
if(dfn[x]<dfn[y]) return x;
return y;
}
inline int chk2(int x,int y)
{
if(dfn[sdom[x]]<dfn[sdom[y]]) return x;
return y;
}
void dfs(int u)
{
dfn[u]=++tim;
a[tim]=u;
d[deep[u]].push_back(u);
for(int i=head[u];i;i=ed[i].next)
{
int v=ed[i].to;
if(dfn[v]) continue;
tr[i]=1;
deep[v]=deep[u]+1;
dfs(v);
}
}
int find(int x)
{
if(x==fa[x]) return x;
int y=fa[x];
fa[x]=find(y);
if(y^fa[x]) c[x]=chk2(c[x],c[y]); //带权并查集 c[x] 维护 x 到 fa 的最小值(不包括 fa)
return fa[x];
}
int main()
{
int n=read(),m=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
add_edge(u,v);
b[v].push_back(u);
}
deep[1]=1;
dfs(1);
//get sdom
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=n;i>=1;i--)
{
int x=a[i];
for(int j=0;j<b[x].size();j++)
{
int y=b[x][j];
sdom[x]=chk(sdom[x],y);
find(y);
sdom[x]=chk(sdom[x],chk(sdom[c[y]],sdom[c[fa[y]]]));
}
c[x]=x;
for(int j=head[x];j;j=ed[j].next)
{
int y=ed[j].to;
if(tr[j]) fa[y]=x; //特判树边
}
}
//get idom
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++) wb[deep[sdom[i]]]++;
for(int i=n;i>=1;i--) wb[i]+=wb[i+1];
for(int i=1;i<=n;i++) a[wb[deep[sdom[i]]]--]=i;
int p=n+1;
for(int i=1;i<=n;i++)
{
int x=a[i];
while(p>deep[sdom[x]])
{
p--;
for(int j=0;j<d[p].size();j++)
{
int y=d[p][j];
c[y]=y;
for(int k=head[y];k;k=ed[k].next)
{
int z=ed[k].to;
if(tr[k]) fa[z]=y; //特判树边
}
}
}
int y=find(x);
if(sdom[c[x]]==sdom[x]) idom[x]=sdom[x];
else lk[x]=c[x];
}
for(p=1;p<=n;p++)
{
for(int i=0;i<d[p].size();i++)
{
int u=d[p][i];
if(!idom[u]) idom[u]=idom[lk[u]];
}
}
idom[1]=0;
for(int i=1;i<=n;i++) size[i]=1;
for(p=n;p>=1;p--)
{
for(int i=0;i<d[p].size();i++)
{
int u=d[p][i];
size[idom[u]]+=size[u];
}
}
for(int i=1;i<=n;i++) cout<<size[i]<<' ';
return 0;
}
/*by DT_Kang*/