一、前言
想写什么写什么,所以这篇文章除了我,可能不适合其他人学习。
其实就是无情的搬运工,并且懒得搬证明。
而且有些地方个人认为有些问题并自行纠错,有可能我反而搞错了,谨慎阅读。
二、讲解
(零)、前提
存在起点 (s) 的连通有向图。
(一)、基本概念
0.符号含义与特别说明
树一般指(dfs)树。树边、返祖边、前向边、横叉边也很好理解。
(u ightarrow v):表示存在一条 (u) 到 (v) 的边。
(uoverset{+}{ ightarrow} v):表示存在一条 (u) 到 (v) 的树边且 (u ot=v)。
(uoverset{.}{ ightarrow} v):表示存在一条 (u) 到 (v) 的树边但允许 (u=v)。
(uleadsto v):表示存在一条由 (u) 到 (v) 的路径。
在本文中,(dfn)值 和(dfs)序等价。
1.最近支配点idom
点 (i) 的支配点中(dfs)序最大的点即为点 (i) 的最近支配点,换句话说,点 (i) 在支配树中的父亲即它的最近支配点。最近支配点一般用 (idom) 表示。
2.半支配点sdom
顶点 (v) 的半支配点 (u) 是所有符合下列条件的点中 (dfs)序 最小的点:
- 顶点 (i) 存在一条路径到 (v) 且路径上的顶点(不包含两个端点)的(dfs)序均大于(v)的(dfs)序。
(v) 的半支配点记为 (sdom(v))。
特别的,若 (u ightarrow v),(u) 也是 (sdom(v)) 的候选点。这种情况相当于路径上没有其他点。
(二)、五大引理
1.
除 (s) 以外的每个点存在唯一的 (idom)。
证明:过于显然,略。
2.
(forall w ot=s),有 (idom(w)overset{+}{ ightarrow}w)。
即 (idom(w)) 是树中 (w) 的祖先。
证明:反证,考虑如果去掉 (idom(w)),(s) 可以通过树边到达 (w)。
3.
(forall w ot=s),有 (sdom(w)overset{+}{ ightarrow}w)。
即 (sdom(w)) 是树中 (w) 的祖先。
证明:咕。
4.
(forall w ot=s),有 (idom(w)overset{.}{ ightarrow}sdom(w))。
即 (idom(w)) 要么是 $sdom(w) 的祖先,要么是 (sdom(m)) 本身。
证明:根据引理二和引理三再反证即可。
5.
对于满足 (uoverset{.}{ ightarrow}v) 的点 (u,v),则有 (uoverset{.}{ ightarrow}idom(v)) 或 (idom(v)overset{.}{ ightarrow}idom(u))
读者自证不难。
(三)、三项定理
1.
(forall u ot=s),如果对于所有满足 (sdom(u)overset{+}{ ightarrow}voverset{.}{ ightarrow}u) 的 (v),有 (dfn(sdom(v))ge dfn(sdom(u))),则有 (idom(u)=sdom(u))。
读者自证不难。
2.
(forall u ot=s),如果有 (sdom(u)overset{+}{ ightarrow}voverset{.}{ ightarrow}u),设 (v) 是满足 (dfn(sdom(v))) 最小的一个 (v),若 (dfn(sdom(v)) < dfn(sdom(u))),则有 (idom(u)=idom(v))。
读者自证不难。
3.
?
读者自证不难。
(四)、重要推论
(forall u ot=s),令 (u) 为所有满足 (sdom(v)overset{+}{ ightarrow}uoverset{.}{ ightarrow}v) 的 (u) 中 (dfn(sdom(u))) 最小的一个点,有:
(五)、具体实现
1.(O(nlog_2n))
-
如果是一棵树,那么它本身就是支配树。
-
如果是一个 (DAG),那么可以拓扑排序,依次确定每个点的 (idom)。具体操作为在原图中找出其所有前驱在支配树上的 (LCA),这个就是它的 (idom)。
-
如果是一个普通有向图,那么我们可以先求出每个点的 (sdom),然后删掉所有非树边并连边 ((sdom(u),u)),此时得到一个 (DAG),支配关系与原图一致。
2.(O(alpha(n)n)))
利用推论即可。
三、练习
四、代码
板题 ((O(nlog_2n)))
//12252024832524
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 200005;
const int MAXM = 300005 << 1;
int n,m;
LL Read()
{
LL x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}
struct EDGE
{
int head[MAXN],tot;
struct edge
{
int v,nxt;
}e[MAXM];
void Add_Edge(int x,int y)
{
e[++tot].v = y;
e[tot].nxt = head[x];
head[x] = tot;
}
}e,re,eg,reg,dt;//原图(边),反图(边),等价图,反等价图,支配树
int dfn[MAXN],rdfn[MAXN],dfntot,fa[MAXN];
void dfs(int x)
{
rdfn[dfn[x] = ++dfntot] = x;
for(int i = e.head[x]; i ;i = e.e[i].nxt)
{
int v = e.e[i].v;
if(dfn[v]) continue;
fa[v] = x;
dfs(v);
eg.Add_Edge(x,v);
}
}
int F[MAXN],sdom[MAXN],MIN[MAXN];
int findSet(int x)
{
if(x == F[x]) return F[x];
int ftr = F[x]; F[x] = findSet(F[x]);
if(dfn[sdom[MIN[x]]] > dfn[sdom[MIN[ftr]]]) MIN[x] = MIN[ftr];
return F[x];
}
void Tarjan()
{
for(int i = 1;i <= n;++ i) sdom[i] = MIN[i] = F[i] = i;
for(int i = n;i > 1;-- i)//枚举的是dfs序
{
int x = rdfn[i];//当前更新节点
if(!x) continue;//不连通
int ret = i;
for(int j = re.head[x]; j ;j = re.e[j].nxt)
{
int v = re.e[j].v;
if(!dfn[v]) continue;
if(dfn[v] < dfn[x]) ret = Min(ret,dfn[v]);
else
{
findSet(v);
ret = Min(ret,dfn[sdom[MIN[v]]]);
}
}
sdom[x] = rdfn[ret];
F[x] = fa[x];
eg.Add_Edge(sdom[x],x);
}
}
int d[MAXN],f[MAXN][18];
int lca(int x,int y)
{
if(x == y) return x;
if(d[x] < d[y]) swap(x,y);
for(int i = 17;i >= 0;-- i)
if(d[f[x][i]] >= d[y]) x = f[x][i];
if(x == y) return x;
for(int i = 17;i >= 0;-- i)
if(f[x][i] != f[y][i])
x = f[x][i],y = f[y][i];
return f[x][0];
}
void bdt(int x)//build dominating tree
{
int LCA = 0;
for(int i = reg.head[x]; i ;i = reg.e[i].nxt)
{
int v = reg.e[i].v;
if(!LCA) LCA = v;
else LCA = lca(LCA,v);
}
f[x][0] = LCA;
d[x] = d[LCA] + 1;
dt.Add_Edge(LCA,x);
for(int i = 1;i <= 17;++ i) f[x][i] = f[f[x][i-1]][i-1];
}
int in[MAXN];
void topu()
{
for(int x = 1;x <= n;++ x)
for(int i = eg.head[x]; i ;i = eg.e[i].nxt)
{
int v = eg.e[i].v;
in[v]++;
reg.Add_Edge(v,x);
}
queue<int> q;
for(int i = 1;i <= n;++ i) if(!in[i]) q.push(i),bdt(i);
while(!q.empty())
{
int x = q.front(); q.pop();
for(int i = eg.head[x]; i ;i = eg.e[i].nxt)
{
int v = eg.e[i].v;
if(!(--in[v])) q.push(v),bdt(v);
}
}
}
int siz[MAXN];
void dtdfs(int x)
{
siz[x] = 1;
for(int i = dt.head[x],v; i ;i = dt.e[i].nxt)
v = dt.e[i].v,dtdfs(v),siz[x] += siz[v];
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n = Read(); m = Read();
for(int i = 1,u,v;i <= m;++ i) u = Read(),v = Read(),e.Add_Edge(u,v),re.Add_Edge(v,u);
dfs(1);
Tarjan();
topu();
dtdfs(0);
for(int i = 1;i <= n;++ i) Put(siz[i],' ');
return 0;
}
五、吐槽
不会真有人写 (O(nlog_2n)) 的算法吧。