边双连通分量

       前面我们已经知道, 对与一个有向图,他有强联通分量,通过强连通分量的缩点,我们可以优化很多问题,那么对于一个无向图,他也有类似于强联通分量的东西,边双连通分量,点双连通分量,这里我来介绍一下边双连通分量

        边双连通分量:在一张连通的无向图中,对于两个点  和 ,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说  和  边双连通

        这里引入一个桥的概念,就是对于一个边如果去掉了这条边,这个图就不能完全连通,那么这条边就称之为桥;

        这里给一道例题,看看如何用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;
}

没有注释但是一看就懂。

完结,撒花!!!!!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值