【概述】
给定一个有向图与一个起点 S,如果要去掉起点 S 到某个点 p 的中间的某个点 x 后无法到达,那么称点 x 支配点 p,x 是 p 的一个支配点。
显然支配点 x 可以有多个,支配点的集合记为 {Xp},对于起点以为的点,它们都有两个平凡的支配点,一个是其自身,一个是起点。
在支配 p 的点中,若一个支配点 i≠p,满足 i 被 p 剩下的所有非平凡的支配点支配,则称 i 为 p 的最近支配点,记作:idom(p)
将图的起点记作 root,那么除 root 外的每一点,均存在唯一的最近支配点,因此,连上所有 root 以外的 idom(u)->u 的边,就能得到一棵树,其中每个点支配它子树中的所有点,这棵树即为支配树。
对于树来说,其本身就是自己的支配树
对于 DAG 来说,可以简单的看做是拓扑排序加上 LCA 求最近公共祖先的问题
对于一般有向图来说,采用 Lengauer Tarjan 算法来解决
【DAG 图】
对于 DAG 图来说,如果要询问从点 x 到点 y 之间有多少个点被删掉后,使得这两个点中的任何一个无法到达另一个,那么我们可以建立支配树来解决。
对于 DAG 图,我们首先进行反向拓扑,然后按照拓扑序建立支配树
由于某点的支配树上的父亲就是该点在 DAG 中的所有出点在支配树上的 LCA,因此对于询问 query(x,y),结果也就是 x 的深度与 y 的深度的和减去 LCA(x,y) 的深度
int n, m, q;
vector<int> G[N]; //原图
vector<int> Gf[N]; //反图
int in[N];
int a[N], tot;
int deep[N], dp[N][22];
void topSort() { //对反图进行拓扑排序
queue<int> Q;
for (int i = 1; i <= n; i++)
if (in[i] == 0)
Q.push(i);
while (!Q.empty()) {
a[++tot] = Q.front();
Q.pop();
int now = a[tot];
for (int i = 0; i < Gf[now].size(); i++) {
int to = Gf[now][i];
in[to]--;
if (in[to] == 0)
Q.push(to);
}
}
}
int getLCA(int x, int y) { //获取LCA
if (deep[x] < deep[y])
swap(x, y);
int cnt = deep[x] - deep[y];
for (int i = 0; i < 20; i++)
if ((1 << i) & cnt)
x = dp[x][i];
if (x == y)
return x;
for (int i = 19; i >= 0; i--) {
if (dp[x][i] != dp[y][i]) {
x = dp[x][i];
y = dp[y][i];
}
}
return dp[x][0];
}
void init() {
tot = 0;
memset(in, 0, sizeof(in));
memset(dp, 0, sizeof(dp));
for (int i = 0; i <= n; i++) {
G[i].clear();
Gf[i].clear();
}
}
int query(int x, int y) {
return deep[x] + deep[y] - deep[getLCA(x, y)];
}
int main() {
init();
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
G[x].push_back(y);
Gf[y].push_back(x);
in[x]++;
}
topSort();
for (int i = 1; i <= n; i++) {
int x = a[i];
if (G[x].size() == 0) {
G[0].push_back(x);
deep[x] = 1;
continue;
}
int y = G[x][0];
for (int i = 1; i < G[x].size(); i++)
y = getLCA(y, G[x][i]);
deep[x] = deep[y] + 1;
dp[x][0] = y;
for (int i = 1; i < 20; i++)
dp[x][i] = dp[dp[x][i - 1]][i - 1];
}
scanf("%d", &q);
while (q--) {
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", query(x, y));
}
return 0;
}
【Lengauer Tarjan 算法】
从点 y 出发到点 x,若存在这样一条路径:路径上的点(不包含 x、y 点)的 dfn 均大于 dfn[x],此时,称 y 是 x 的半支配点
用 sdom[x] 表示 x 的半必经点中 dfn 最小的点,这样一来,当删除原图中的非树边,然后连接 (sdom[x],x) 后,不仅不会改变原图中的支配点关系,而且还将原图转为一个 DAG 图,这样就可以使用 DAG 的做法了
但对于一个一般有向图来说,除了上述方法外,利用 Lengauer Tarjan 算法可以快速构造支配树的算法,其可在 O(nlogn) 的时间复杂度内构造一个支配树。
1.算法原理
1)构建 dfs 树
首先对图进行 dfs 遍历,求出 dfs 序
2)找半支配点
对于一个点 x,要找出所有的边 (y,x) 中对应的 y,那么有:
- 若 dfn[y]<dfn[x] 且 dfn[y] 比当前找到的 sdom[x] 的 dfn 小,那么有 sdom[x]=y
- 若 dfn[x]>dfn[y],那么找到树上 y 的一个满足 dfn[z]>dfn[x] 的祖先 z,然后比较 dfn[sdom[z]] 与 dfn[sdom[x]] 的关系,来决定是否用前者更新后者
3)半支配点到支配点
对于点 x 来说,我们要找的是 idom[x],当求出 sdom[x] 后,我们需要利用 sdom[x] 来找出 idom[x]
令 P 是从 sdom[x] 到 x 的树上路径点集,且 P 中不包含 sdom[x],并设 y 是 P 中 dfn[sdom[y]] 最小的点
那么:如果 sdom[y]=sdom[x],则 idom[x]=sdom[x],否则 idom[x]=idom[z]
2.算法流程
Lengauer Tarjan 算法的实现一般采用带权并查集
首先对原图进行一遍 dfs 来建立 dfs 树并求出 dfs 序,然后按照 dfs 序从大到小进行处理,利用带权并查集寻找每个点的半支配点 sdom
每一次处理完毕一个点后,将这个点同其在 dfs 树上的父亲在并查集进行连边,边的权值是这个点到根结点的路径上的所有点中最小的 dfn[sdom[x]] 对应的 x
然后根据 dfs 的入度建立 dfs 树的反图,并进行拓扑排序,建立支配树
最后直接在支配树上进行 dfs,根据求出的 sdom[i] 直接计算 idom[i] 即可
3.模版
struct MAP {
struct Edge {
int to, next;
} edge[N << 1];
int tot, head[N];
void addEdge(int x, int y) {
edge[++tot].to = y;
edge[tot].next = head[x];
head[x] = tot;
}
};
MAP G, GF; //原图、反图
MAP dfsTree, dfsTreeF; // dfs树、dfs树的反图
MAP dominate; //支配树
MAP xx;
int n, m;
int father[N]; // dfs树上的父节点
int dfn[N], id[N], tim; // dfs序、标号、时间戳
void dfs(int x) {
id[++tim] = x;
dfn[x] = tim;
for (int i = G.head[x]; i; i = G.edge[i].next) {
int to = G.edge[i].to;
if (!dfn[to]) {
dfs(to);
father[to] = x;
dfsTree.addEdge(x, to);
}
}
}
int sdom[N]; //半支配点
int mn[N]; // mn[i]表示i点的dfs树上的sdom最小的祖先,因此有sdom[mn[i]]=sdom[i]
int anc[N]; // anc[i]代表i的祖先
int find(int x) { //路径压缩的带权并查集
if (x != anc[x]) {
int t = anc[x];
anc[x] = find(anc[x]);
if (dfn[sdom[mn[x]]] > dfn[sdom[mn[t]]])
mn[x] = mn[t];
}
return anc[x];
}
void LengauerTarjan() { //寻找半支配点
for (int i = 1; i <= n; i++) {
anc[i] = i;
sdom[i] = i;
mn[i] = i;
}
for (int j = n; j >= 2; j--) {
int x = id[j];
if (!x)
continue;
int pos = j;
for (int i = GF.head[x]; i; i = GF.edge[i].next) {
int y = GF.edge[i].to;
if (!dfn[y])
continue;
if (dfn[y] < dfn[x])
pos = min(pos, dfn[y]);
else {
find(y); //寻找树上y的一个满足dfn[z]>dfn[x]的祖先z
pos = min(pos, dfn[sdom[mn[y]]]);
}
}
sdom[x] = id[pos];
anc[x] = father[x];
dfsTree.addEdge(sdom[x], x); //在dfs树上连边
}
}
int deep[N], dp[N][25];
int getLCA(int x, int y) { //获取LCA
if (deep[x] < deep[y])
swap(x, y);
int del = deep[x] - deep[y];
for (int i = 0; i <= 20; i++)
if ((1 << i) & del)
x = dp[x][i];
if (x == y)
return x;
for (int i = 20; i >= 0; i--) {
if (dp[x][i] != dp[y][i]) {
x = dp[x][i];
y = dp[y][i];
}
}
return dp[x][0];
}
void buildDominate(int x) { //建立支配树
int to = dfsTreeF.edge[dfsTreeF.head[x]].to;
for (int i = dfsTreeF.head[x]; i; i = dfsTreeF.edge[i].next) {
int y = dfsTreeF.edge[i].to;
to = getLCA(to, y);
}
deep[x] = deep[to] + 1;
dp[x][0] = to;
dominate.addEdge(to, x);
for (int i = 1; i <= 20; i++)
dp[x][i] = dp[dp[x][i - 1]][i - 1];
}
int in[N]; // dfs树的入度
void topSort() {
for (int i = 1; i <= n; i++) {
for (int j = dfsTree.head[i]; j; j = dfsTree.edge[j].next) {
int to = dfsTree.edge[j].to;
in[to]++;
dfsTreeF.addEdge(to, i); //对DFS树的反图建边
}
}
for (int i = 1; i <= n; i++) {
if (!in[i]) {
dfsTree.addEdge(0, i); // dfs树建边
dfsTreeF.addEdge(i, 0); // dfs树的反图建边
}
}
queue<int> Q;
Q.push(0);
while (Q.size()) {
int x = Q.front();
Q.pop();
for (int i = dfsTree.head[x]; i; i = dfsTree.edge[i].next) {
int y = dfsTree.edge[i].to;
if ((--in[y]) <= 0) {
Q.push(y);
buildDominate(y); //建立支配树
}
}
}
}
int idom[N];
void dfsDominate(int x) { //在支配树上搜索idom
idom[x] = 1;
for (int i = dominate.head[x]; i; i = dominate.edge[i].next) {
int y = dominate.edge[i].to;
dfsDominate(y);
idom[x] += idom[y];
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
G.addEdge(x, y);
GF.addEdge(y, x);
}
dfs(1); // dfs,求出dfs序
LengauerTarjan(); //计算半支配点sdom
topSort(); //根据dfs树建立dfs树的反图,并对进行拓扑排序从而建立支配树
dfsDominate(0); //在支配树上寻找答案
for (int i = 1; i <= n; i++)
printf("%d ", idom[i]);
printf("\n");
return 0;
}