Tarjan(五)vDCC缩点

Tarjan(五)

vDCC点双联通分量: 需要之前的前置知识,需要搞懂什么是割点。在tarjan(2)中有介绍到。

点双连通分量是指在一个无向图中,如果一个子图是点双连通的(即去掉该子图中的任意一个节点后,剩余的图仍然是连通的,不存在割点),则称这个子图为点双连通分量。换句话说,点双连通分量是图中的极大点双连通子图。

  1. 无割点:点双连通分量中不包含割点,即去掉该分量中的任意一个节点,剩余的图仍然是连通的。
  2. 路径多样性:在点双连通分量中,任意两点间至少存在两条“点不重复”的路径。这意味着,即使去掉了连接这两点的某一条路径上的所有节点(除了起点和终点),这两点之间仍然可以通过其他路径相连。
  3. 割点与点双连通分量的关系:任意割点都是至少两个点双连通分量的公共点。这是因为割点至少连接着图的两部分,而这两部分由于不能包含割点,所以分别属于不同的点双连通分量。

如上图:存在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

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幻听嵩的留香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值