D19 vDCC 缩点 Tarjan 算法

D19 vDCC 缩点 Tarjan 算法_哔哩哔哩_bilibili

 

双连通分量 - OI Wiki

点双连通分量(vDCC)

先给出两个性质:

  1. 两个点双最多只有一个公共点,且一定是割点.
  2. 对于一个点双,它在 DFS 搜索树中 dfn 值最小的点一定是割点或者树根.

我们根据第二个性质,分类讨论:

  1. 当这个点为割点时,它一定是点双连通分量的根,因为一旦包含它的父节点,他仍然是割点.
  2. 当这个点为树根时:
    • 有两个及以上子树,它是一个割点.
    • 只有一个子树,它是一个点双连通分量的根.
    • 它没有子树,视作一个点双.

 

P8435 【模板】点双连通分量 - 洛谷

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

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

const int N=500010;
int n,m;
vector<int> e[N],dcc[N];
int dfn[N],low[N],tot,stk[N],top,cut[N],root,cnt;

void tarjan(int u){
  if(u==root && !e[u].size()){ //孤立点
    dcc[++cnt].push_back(u);
    return;
  }
  
  dfn[u]=low[u]=++tot; stk[++top]=u;
  int son=0;
  for(int v:e[u]){
    if(!dfn[v]){ //若v未访问
      tarjan(v);
      low[u]=min(low[u],low[v]);
      
      if(low[v]>=dfn[u]){ //u是割顶
        son++;
        if(u!=root||son>1)cut[u]=1; //记录割点
        
        // 每次回到割顶都要记录vDCC
        ++cnt;
        for(int z=-1;z!=v;){ //让u留在栈中
          z=stk[top--];
          dcc[cnt].push_back(z);
        }
        dcc[cnt].push_back(u); //记录vDCC
      }
    }
    else //若v已访问
      low[u]=min(low[u],dfn[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;
    if(a==b) continue; //忽略自环
    e[a].push_back(b),
    e[b].push_back(a);
  }
  for(root=1;root<=n;root++)if(!dfn[root])tarjan(root);
  cout<<cnt<<"\n";
  for(int i=1;i<=cnt;i++){
    cout<<dcc[i].size()<<" ";
    for(int j:dcc[i])cout<<j<<" ";
    cout<<"\n";
  }
}

 

vDCC 缩点

vDCC:不删除边,分裂割点,构成的连通块

把 vDCC 缩成点,把缩点和割点对应连边,构成树

1. cut[x] 记录割点,dcc[] 记录 vDCC,id[] 割点的编号

2. 每次回溯:判割点,记录 vDCC

3. 特判孤立点,忽略自环

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

const int N=10010;
int n,m,a,b;
vector<int> e[N],ne[N],dcc[N];
int dfn[N],low[N],tot,stk[N],top,cut[N],root,cnt;
int id[N];

void tarjan(int x){
  dfn[x]=low[x]=++tot;
  stk[++top]=x;
  if(x==root&&!e[x].size()){ //孤立点
    dcc[++cnt].push_back(x);
    return;
  }
  int son=0;
  for(int y:e[x]){
    if(!dfn[y]){ //若y未访问
      tarjan(y);
      low[x]=min(low[x],low[y]); 
      if(low[y]>=dfn[x]){
        son++;
        if(x!=root||son>1)cut[x]=1; //割点
        
        ++cnt;
        while(1){
          int z=stk[top--];
          dcc[cnt].push_back(z);
          if(z==y) break; //让x留在栈中
        }
        dcc[cnt].push_back(x); //vDCC
      }
    }
    else //若y已访问
      low[x]=min(low[x],dfn[y]);
  }
}
int main(){
  cin>>n>>m;
  while(m--){
    cin>>a>>b;
    e[a].push_back(b),
    e[b].push_back(a);
  }
  for(root=1;root<=n;root++)if(!dfn[root])tarjan(root);
      
  //给每个割点一个新编号(cnt+1开始)
  int num=cnt;
  for(int i=1;i<=n;i++)if(cut[i])id[i]=++num;
  //建树,从每个缩点向对应割点连边
  for(int i=1;i<=cnt;i++){
    for(int j=0;j<dcc[i].size();j++){
      int x=dcc[i][j];
      if(cut[x]){
        ne[i].push_back(id[x]),
        ne[id[x]].push_back(i);        
      }
    }
  }
}

 

posted @ 2022-05-28 13:33  董晓  阅读(1342)  评论(2)    收藏  举报