Tarjan(五)
vDCC点双联通分量: 需要之前的前置知识,需要搞懂什么是割点。在tarjan(2)中有介绍到。
点双连通分量是指在一个无向图中,如果一个子图是点双连通的(即去掉该子图中的任意一个节点后,剩余的图仍然是连通的,不存在割点),则称这个子图为点双连通分量。换句话说,点双连通分量是图中的极大点双连通子图。
- 无割点:点双连通分量中不包含割点,即去掉该分量中的任意一个节点,剩余的图仍然是连通的。
- 路径多样性:在点双连通分量中,任意两点间至少存在两条“点不重复”的路径。这意味着,即使去掉了连接这两点的某一条路径上的所有节点(除了起点和终点),这两点之间仍然可以通过其他路径相连。
- 割点与点双连通分量的关系:任意割点都是至少两个点双连通分量的公共点。这是因为割点至少连接着图的两部分,而这两部分由于不能包含割点,所以分别属于不同的点双连通分量。
如上图:存在1、5两个割点,其中的联通分量有四个,用代码实现上述过程:
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
const int N = 1e4 + 10, M = 2e5 + 10;
int n, m;
vector<int> e[N], ne[N]; // 对于点联通分量直接用领接表存边,没必要像边双联通分量一样把边按编号存起来,因为割点和割边的差异导致:割点允许往返走,例如可以从a->b,再从b->a,但是割边不行。
vector<int> vdcc[N];//记录点双联通分量
int dfn[N], low[N], tot, root; // root在割点中介绍过,判断某个节点是不是割点用的
int cut[N], idx,cnt; //cut[i]为1则表示i为割点
stack<int> stk; //方便记录点双联通分量
void tarjan(int x) {
dfn[x] = low[x] = ++tot;
cout << "dfn[" << x << "]=" << dfn[x] << '\n';
if(!e[x].size()) { //孤立点
cut[++idx] = x;
cout << "eDCC:" << x << '\n';
return;
}
stk.push(x);
int child = 0;
for(int y : e[x]) {
if(!dfn[y]) {
tarjan(y);
low[x] = min(low[x], low[y]);
cout << "low[" << x << "]=" << low[x] << '\n';
if(low[y] >= dfn[x]) {
cout << "eDCC:";
child++;
if(x != root || child > 1)
cut[x] = 1;
cnt++;
while(1) { //和边双联通分量不同,vDCC在if(low[y] >= dfn[x])里面进行判断,不要写在if(x != root || child > 1)这个判断里面,因为这个是判断割点的,对于每个割点,至少属于两个vDCC,但是你只处理了一遍while循环,
int t = stk.top(); stk.pop();
cout << t << ' ';
vdcc[cnt].push_back(t);
if(t == y) break; //这个条件已经不再是t == x,因为若x为割点,则x至少分别是两个点双联通分量的结点
}
vdcc[cnt].push_back(x); //所以x单独通过加入,而不是出栈x,因为可能是其他vdcc的结点。
cout << x << '\n';
}
}else {
low[x] = min(low[x], dfn[y]);
cout << "*low[" << x << "]=" << low[x] << '\n';
}
}
}
int main() {
cin >> n >> m;
while(m--) {
int a, b; cin >> a >> b;
e[a].push_back(b);
e[b].push_back(a);
}
for(root = 1; root <= n; root++) {
if(!dfn[root]) tarjan(root);
}
return 0;
}
输出如上,可以看到输出中总共有4个联通块
那最终经过点双联通分量缩点后怎么建图,我们可以看到:
我们的vdcc点双联通分量共有4个,分别是上面的1,2,3,4.那5、6怎么来的?其实便是之前记录下来的割点cut[i],之前的1、5结点便是割点,只不过在新图后我们重新编号,从4后面编号。整个过程就好像原来的割点裂开分成多个,例如原来的1号结点,不仅出现在新图中的1联通块中,还出现在4联通块中,其实5结点也是原来的1号结点。
int num = cnt;
for(int i = 1; i <= n; i++) {
if(cut[i]) gd[i] = ++num;
}
for(int i = 1; i <= cnt; i++) {
for(int j = 0; j < vdcc[i].size(); j++) {
int x = vdcc[i][j];
if(cut[x]) {
ne[gd[x]].push_back(i);
ne[i].push_back(gd[x]);
}
}
}
for(int i = 1; i <= cnt; i++) {
cout << "vdcc: " << i << ':';
for(int j = 0; j < ne[i].size(); j++) {
cout << ne[i][j] << ' ';
}
cout << '\n';
}
如果还有疑惑,建议去看视频讲解:【D19 Tarjan vDCC 缩点】https://www.bilibili.com/video/BV18Z4y1v7tt?vd_source=4c9eb38d8205116069b961c84f64c958