在一个有向图中,确定 S S S作为起点。对某个点 x x x而言,如果点 y y y是 x x x的支配点,则从 S S S到 x x x的任意路径均必须经过 y y y。显然支配点可能不止一个。但如果将 x x x的最近支配点到 x x x连一条边,则会形成一个树形结构,称之为支配树。
假设有图
digraph demo{
1 -> {2}
2 -> {3}
3 -> {4, 5, 6}
4 -> {1, 7}
5 -> {3, 6}
6 -> {8}
7 -> {8, 9, 10}
}
且可视化如下
则其支配树是
从 S S S开始做一个深搜,依次进入的点的顺序即为dfs序。上图的dfs序为
1 2 3 4 7 8 9 10 5 6
dfs序显然不是唯一的,一般而言,书面表示还会尽量写成升序形式以方便阅读。
x x x节点的半支配点 y y y是指: 1. 从 y y y到 x x x有一条路径,其中间点(不包括 x x x和 y y y本身)的 d f s dfs dfs序均大于 x x x的 d f s dfs dfs序;2. 且 y y y的 d f s dfs dfs序是所有满足上述条件的点中最小的那个。半支配点显然是唯一的。
在上述例子中, { 1 , 2 , 3 , 4 , 5 } \{1,2,3,4,5\} {1,2,3,4,5}是一个强连通分量,考虑其到8的路径,显然只有
3 -> 6 -> 8
3 -> 5 -> 6 -> 8
5 -> 6 -> 8
这三条路径满足条件1,而其中节点 3 3 3是 d f s dfs dfs序最小的。因此 8 8 8的半支配点是 3 3 3。
根据半支配点的定义,很容易写出一个递归的写法:
当然暴力维护是不行的,需要安排好迭代顺序与数据结构。
令 S d o m i Sdom_i Sdomi是i的当前的半支配点,即当前能够找到的符合条件的dfs序最小的点; m n i mn_i mni表示节点i的祖先中dfs序最小的节点, u n i i uni_i unii表示。
假设 y y y是 x x x的半支配点,同时 u u u是 y y y到 x x x路径上的一个点, u u u的半支配点 v v v的 d f s dfs dfs序是所有 y y y到 x x x路径上点中最小的,则 x x x的支配点有两种情况:
可以证明,不会证明。
下述算法是核心算法,用于求取半支配点以及部分支配点(第一种情况)。
for i in range(N, 1): # 按照dfs序的逆序遍历
u = Ord[i] # 排名第i位的节点记作u
for v in ig[u]: # 遍历u的入点
uni_query(v) # 对v做一个并查集的查询,
if Dfn[Sdom[mn[v]]] < Dfn[Sdom[u]]: # mn[v]表示v的祖先中半支配点dfs序最小的那个祖先
Sdom[u] = Sdom[mn[v]] # 更新半支配点
uni[u] = Parent[u] # 并查集合并
SdomTree[Sdom[u]].append(u) # Sdom[u]到u引一条边
u = Parent[u]
for v in SdomTree[u]:
uni_query(v) # 再次做一个查询,主要是为了更新uni[v]
# u是v的半支配点,mn[v]是v的半支配点dfs序最小的那个祖先,如果if成立就是上文中的第一种情况
# 否则,暂时将Idom[v]记作mn[v]
Idom[v] = u if u == Sdom[mn[v]] else mn[v]
洛谷支配树模板题代码如下。
#include
using namespace std;
struct DominatorTree_Lengauer_Tarjan{
using vi = vector<int>;
using vvi = vector<vi>;
vvi g; // 1-indexed
vvi ig;
void init(int n){
g.assign(n + 1, vi());
ig.assign(n + 1, vi());
}
void mkEdge(int a, int b){
g[a].push_back(b);
ig[b].push_back(a);
}
int S;
vi Dfn; // Dfn[i]表示节点i的dfs序
vi Ord; // Ord[i]表示排名第i位的节点
vi Parent; // Parent[i]表示深搜树上i节点的父节点
int Stamp;
vi uni; // 带权并查集的father数组
vi mn; // mn[i]表示i的所有祖先中,半支配点的dfs序最小的那个祖先
vi Sdom; // Sdom[i]表示i的半支配点
vi Idom; // Idom[i]表示i的直接支配点
vvi SdomTree;
void Lengauer_Tarjan(int s){
int n = this->g.size() - 1;
/// 确定dfs序
Dfn.assign(this->g.size(), Stamp = 0);
Ord.assign(this->g.size(), 0);
Parent.assign(this->g.size(), 0);
dfs(S = s);
/// 求解半支配点与支配点的中间答案
Sdom.assign(this->g.size(), 0);
uni.assign(this->g.size(), 0);
mn.assign(this->g.size(), 0);
SdomTree.assign(this->g.size(), vi());
Idom.assign(this->g.size(), 0);
calcSdom();
/// 最后确定支配点
for(int i=2;i<=n;++i){
int u = Ord[i];
if(Idom[u] ^ Sdom[u]){
Idom[u] = Idom[Idom[u]];
}
}
return;
}
void dfs(int u){
Ord[Dfn[u] = ++Stamp] = u;
for(auto v : g[u]){
if(0 == Dfn[v]){
Parent[v] = u;
dfs(v);
}
}
return;
}
void calcSdom(){
int n = this->g.size() - 1;
for(int i=1;i<=n;++i) Sdom[i] = mn[i] = uni[i] = i;
for(int i=n;i>=2;--i){
int u = Ord[i]; // 排名第i位的节点是u
for(int v : ig[u]){
if(0 == Dfn[v]) continue;
uni_query(v);
if(Dfn[Sdom[mn[v]]] < Dfn[Sdom[u]]){
Sdom[u] = Sdom[mn[v]];
}
}
uni[u] = Parent[u]; // 将u向上合并
SdomTree[Sdom[u]].push_back(u);
for(int v : SdomTree[u = Parent[u]]){
uni_query(v);
Idom[v] = (u == Sdom[mn[v]]) ? u : mn[v];
}
SdomTree[u].clear();
}
return;
}
/// 带权并查集的find操作
int uni_query(int u){
if(u == uni[u]) return u;
int ans = uni_query(uni[u]);
/// mn[u]是通过mn[uni[u]]计算出来的
if(Dfn[Sdom[mn[uni[u]]]] < Dfn[Sdom[mn[u]]]) mn[u] = mn[uni[u]];
return uni[u] = ans;
}
};
int N, M;
DominatorTree_Lengauer_Tarjan D;
int main(){
#ifndef ONLINE_JUDGE
freopen("1.txt", "r", stdin);
#endif
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> N >> M;
D.init(N);
for(int a,b,i=0;i<M;++i){
cin >> a >> b;
D.mkEdge(a, b);
}
D.Lengauer_Tarjan(1);
vector<int> ans(N+1, 0);
for(int i=N;i>=2;--i) ans[D.Idom[D.Ord[i]]] += ++ans[D.Ord[i]];
++ans[1];
for(int i=1;i<=N;++i) cout << ans[i] << " "; cout << endl;
return 0;
}