前面我们已经知道, 对与一个有向图,他有强联通分量,通过强连通分量的缩点,我们可以优化很多问题,那么对于一个无向图,他也有类似于强联通分量的东西,边双连通分量,点双连通分量,这里我来介绍一下边双连通分量
边双连通分量:在一张连通的无向图中,对于两个点 和 ,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说 和 边双连通。
这里引入一个桥的概念,就是对于一个边如果去掉了这条边,这个图就不能完全连通,那么这条边就称之为桥;
这里给一道例题,看看如何用tarjan算法对无向图进行缩点:传送们
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef double D;
const int N = 2e5 + 10;
typedef pair<int, int> Pii;
int e[N],idx,ne[N],h[N];
void add(int a,int b){//链式前向星建图
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
stack<int>stk;
int dfn[N],low[N];//时间戳,和能到达的最小时间戳
int tot=1;//时间戳
int edcc[N],cnt=1;//边双连通分量的编号
int bridge[N];//是否为桥
void tj(int u,int fa){//fa是上一个节点的idx,妙用
low[u]=dfn[u]=tot++;//初始化
stk.push(u);//入栈
for(int i=h[u];i!=-1;i=ne[i]){//遍历子节点
int v=e[i];
if(!dfn[v]){//如果没有访问
tj(v,i);//dfs
low[u]=min(low[u],low[v]);//更新时间戳
if(low[v]>low[u]){//如果这个子节所能到达的点永远比u大,说明u,v俩点之间的边是桥
bridge[i]=bridge[i^1]=1;//存边的时候,01一对,23 对,
//i^1如果i是偶数那么i-1,如果i是奇数i+1
//就如同存了a-->b b-->a,那么e[i]存的是a,e[i+1]存的是b;
}
}
else if(i!=(fa^1)){//如果这个节点不是父亲节点而来的,那么就用这个点去更新u节点的low[u]
low[u]=min(low[u],low[v]);
}
}
if(low[u]==dfn[u]){//如果这个界节点的low[u]==dfn[u]说明这个节点是一个双连通分量里面第一个入栈的
int y;
do{
y=stk.top();
stk.pop();
edcc[y]=cnt;//将同于一个边双连通分量里的点标号
}while(y!=u);//其实就类似于有向图缩点
cnt++;
}
}
int d[N];//记录缩点后边连通分量的度
void solved()
{
int f,r;
cin>>f>>r;
for(int i=1;i<=f;i++){
h[i]=-1;//初始化头节点,memset也可
}
for(int i=1;i<=r;i++){
int a,b;
cin>>a>>b;
add(a,b);//双向建边
add(b,a);
}
tj(1,-1);//缩点
for(int i=0;i<idx;i++){
if(bridge[i]){//如果这个点标记的是桥
d[edcc[e[i]]]++;//那么这个点对应的双连通分量编号度增加
}
}
int ans=0;
//缩完点之后所有的点就形成了一颗树
for(int i=1;i<cnt;i++){//记录数中所有叶子节点
if(d[i]==1)ans++;
}
cout<<(ans+1)/2<<"\n";//那么要将一颗树变成边双连通分量最少需要建立(ans+1)/2条边
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
t=1;
while (t--)
{
solved();
}
return 0;
}
以上的做法是用链式前向星写的,个人感觉比较古老,而且感觉tj里面传入的是idx十分不舒服,在网上好像也很难找到不同的,于是就自己写个比较好理解的
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef double D;
const int N = 2e5 + 10;
typedef pair<int, int> Pii;
vector<int>e[N];
stack<int>stk;
int dfn[N],low[N];
int tot=1;
int edcc[N],cnt=1;
int bridge[N];
void tj(int u,int fa){//fa表示父节点
low[u]=dfn[u]=tot++;
stk.push(u);
for(auto v:e[u]){
if(!dfn[v]){
tj(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>low[u]){
bridge[v]++;bridge[u]++;;
}
}
else if(v!=fa){
low[u]=min(low[u],low[v]);
}
}
if(low[u]==dfn[u]){
int y;
do{
y=stk.top();
stk.pop();
edcc[y]=cnt;
}while(y!=u);
cnt++;
}
}
int d[N];
void solved()
{
int f,r;
cin>>f>>r;
for(int i=1;i<=r;i++){
int a,b;
cin>>a>>b;
e[a].push_back(b);
e[b].push_back(a);
}
tj(1,-1);
for(int i=1;i<=f;i++){
if(bridge[i]){
d[edcc[i]]+=bridge[i];
}
}
int ans=0;
for(int i=1;i<cnt;i++){
if(d[i]==1)ans++;
}
cout<<(ans+1)/2<<"\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
t=1;
while (t--)
{
solved();
}
return 0;
}
没有注释但是一看就懂。
完结,撒花!!!!!