D18 eDCC 缩点 Tarjan 算法

D18 eDCC 缩点 Tarjan 算法_哔哩哔哩_bilibili

 

双连通分量 - OI Wiki

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

对于一个无向图中的 极大 边双连通的子图,我们称这个子图为一个 边双连通分量(eDCC)

对于一张连通的无向图,从一点开始 DFS,得到原图的一棵 DFS 生成树,这棵生成树上的边称作 树边,不在生成树上的边称作 非树边

由于 DFS 的性质,所有非树边连接的两个点在生成树上都满足其中一个是另一个的祖先.

过程

在无向图中只要一个分量没有桥,那么在 DFS 生成树上,它的所有点都在同一个强连通分量中.

在 DFS 生成树上的一个强连通分量,在原无向图中是边双连通分量.

可以发现,求边双连通分量的过程实际上就是求强连通分量的过程.

 

P8436 【模板】边双连通分量 - 洛谷

对于一个 n 个节点 m 条无向边的图,请输出其边双连通分量的个数,并且输出每个边双连通分量。

#include<bits/stdc++.h>
using namespace std;

const int N=500010,M=4000010;
int n,m;
int h[N],to[M],ne[M],idx=1;
void add(int a,int b){
  to[++idx]=b,ne[idx]=h[a],h[a]=idx;
}
int dfn[N],low[N],tim,stk[N],top,cnt;
vector<int> dcc[N];

void tarjan(int u,int e){
  dfn[u]=low[u]=++tim; stk[++top]=u;
  for(int i=h[u];i;i=ne[i]){
    int v=to[i];
    if(!dfn[v]){ //若v未访问
      tarjan(v,i);
      low[u]=min(low[u],low[v]);
      // if(low[v]<dfn[u]) bri[i]=bri[i^1]=1;
    }
    else if(i!=(e^1)) //若v已访问且不是反边
      low[u]=min(low[u],dfn[v]);
  }
  
  if(low[u]==dfn[u]){ //若u是edcc的根
    ++cnt;
    for(int v=-1;v!=u;){
      v=stk[top--];
      dcc[cnt].push_back(v);
    }
  }
}
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin>>n>>m;
  for(int a,b;m--;){
    cin>>a>>b;add(a,b);add(b,a);
  }
  for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,0);
  cout<<cnt<<"\n";
  for(int i=1;i<=cnt;i++){
    cout<<dcc[i].size()<<" ";
    for(int j:dcc[i]) cout<<j<<" ";
    cout<<"\n";
  }
}

 

P2860 [USACO06JAN] Redundant Paths G - 洛谷

给定一个无向连通图,问至少加多少条边使得整个图变成 边双连通分量。

eDCC 缩点
1. 缩点成树:x → dcc[x],n → cnt
2. 记录割边:bri[i]=bri[i^1]=1
3. 统计割边端点的度:du[dcc[to[i]]]++
4. 统计叶节点个数,构造答案 $\left \lfloor \frac{s+1}{2} \right \rfloor$

// Tarjan eDCC缩点 O(n+m)
#include<bits/stdc++.h>
using namespace std;

const int N=5010,M=20010;
int n,m;
int h[N],to[M],ne[M],idx=1;
void add(int a,int b){
  to[++idx]=b,ne[idx]=h[a],h[a]=idx;
}

int dfn[N],low[N],tim,stk[N],top,dcc[N],cnt;
int bri[M],du[N];

void tarjan(int u,int e){
  dfn[u]=low[u]=++tim; stk[++top]=u;
  for(int i=h[u];i;i=ne[i]){
    int v=to[i];
    if(!dfn[v]){ //若v未访问
      tarjan(v,i);
      low[u]=min(low[u],low[v]);
      
      if(dfn[u]<low[v]) bri[i]=bri[i^1]=1;
    }
    else if(i!=(e^1)) //若v已访问且不是反边
      low[u]=min(low[u],dfn[v]);
  }
  
  if(low[u]==dfn[u]){ //若u是edcc的根
    ++cnt;
    for(int v=-1;v!=u;){
      v=stk[top--];
      dcc[v]=cnt;
    }
  }
}
int main(){
  cin>>n>>m;
  for(int a,b;m--;){
    cin>>a>>b,add(a,b),add(b,a);
  }
  tarjan(1,0);
  
  for(int i=2;i<=idx;i++) //枚举边,统计割边端点的度
    if(bri[i]) du[dcc[to[i]]]++;
  int s=0;
  for(int i=1;i<=cnt;i++) //枚举dcc,统计叶节点个数
    if(du[i]==1) s++;
  cout<<(s+1>>1);
}

 

posted @ 2022-05-28 13:32  董晓  阅读(1531)  评论(1)    收藏  举报