对于
D
A
G
DAG
DAG,拓扑排序
+
+
+ 倍增
L
C
A
LCA
LCA 就可以做了(不会的可以先做 [ZJOI2012] 灾难)
对于一般有向图(假设连通),转成等价的
D
A
G
DAG
DAG 后采用上述方法即可
首先构造出一棵
d
f
s
dfs
dfs 树,记录每个点的
d
f
n
dfn
dfn
显然一条边
(
u
,
v
)
(u,v)
(u,v) 如果不在
d
f
s
dfs
dfs 树上,那么
d
f
n
[
u
]
>
d
f
n
[
v
]
dfn[u]>dfn[v]
dfn[u]>dfn[v]
然后定义
x
x
x 的半支配点
s
[
x
]
s[x]
s[x]:
(
1
)
(1)
(1).如果存在任意一条路径
(
y
,
x
)
(y,x)
(y,x),使得这条路径上每个除
x
,
y
x,y
x,y 之外的点
z
z
z 都满足
d
f
n
[
z
]
>
d
f
n
[
x
]
dfn[z]>dfn[x]
dfn[z]>dfn[x],那么称
y
y
y 是合法的
(
2
)
(2)
(2).在所有合法的
y
y
y 中找一个
d
f
n
dfn
dfn 最小的,就是
s
[
x
]
s[x]
s[x]
将所有
s
[
x
]
s[x]
s[x] 向
x
x
x 连边,再加上
d
f
s
dfs
dfs 树,就组成了一个等价的
D
A
G
DAG
DAG
考虑这样为什么是对的
(
1
)
(1)
(1).
s
[
x
]
s[x]
s[x] 一定是
x
x
x 在
d
f
s
dfs
dfs 树上的祖先,因此新图无环
(
2
)
(2)
(2).
s
[
x
]
s[x]
s[x] 到
x
x
x 一定有至少两条边不相交的路径
(
3
)
(3)
(3).
s
[
x
]
s[x]
s[x] 还是所有合法点在
d
f
s
dfs
dfs 树上的祖先,因为
d
f
n
dfn
dfn 最小
注意
s
[
x
]
s[x]
s[x] 不一定支配
x
x
x,如果不存在合法点,那么
s
[
x
]
s[x]
s[x] 为
d
f
s
dfs
dfs 树上的父亲
有什么不懂的画个图即可理解
考虑怎么求
s
[
x
]
s[x]
s[x],按
d
f
n
dfn
dfn 降序枚举
v
v
v,然后枚举边
(
u
,
v
)
(u,v)
(u,v)
维护并查集,记录
v
v
v 已经枚举过的祖先中,
d
f
n
dfn
dfn 最小的那个,记为
m
n
[
v
]
mn[v]
mn[v]
每做完一个点
v
v
v,把
v
v
v 向
f
a
[
v
]
fa[v]
fa[v] 合并
根据
d
f
n
[
u
]
dfn[u]
dfn[u] 和
d
f
n
[
v
]
dfn[v]
dfn[v] 大小关系进行讨论
详见代码,还是那句话,有什么不懂的画个图即可理解
// 求半支配点部分inlineintfind(int x){if(x == fa[x])return x;int t = fa[x];
fa[x]=find(fa[x]);if(dfn[s[mn[t]]]< dfn[s[mn[x]]]) mn[x]= mn[t];return fa[x];}inlinevoidinit(){int i, j;for(i =1; i <= n; i++) fa[i]= mn[i]= s[i]= i;for(i = id[0]; i >=2; i--){int v = id[i], len = h[v].size(), res = n;// res 为最小合法点的 dfn,id[res] 即 dfn 为 res 的点,即 s[x]for(j =0; j < len; j++){int u = h[v][j];if(!dfn[u])continue;// 注意可能图不连通if(dfn[u]< dfn[v]) res =min(res, dfn[u]);else{find(u);
res =min(res, dfn[s[mn[u]]]);// 用 u 的已做过的祖先的最小 dfn 更新 res}}
s[v]= id[res];add(s[v], v);
fa[v]= anc[v];// anc[v] 为 v 的祖先,fa[v] 是并查集,带路径压缩}}
由于拓扑排序
+
+
+ 倍增
L
C
A
LCA
LCA,时间复杂度
O
(
n
log
n
+
m
)
O(n\log n + m)
O(nlogn+m)