20250818 - 割点 割边 总结

前言

会 dfs 序的人都会 tarjan。

概念

连通分量(极大连通子图):极大连通子图就是要使得连通子图的点和边数量尽可能大,注意是极大,不是最大(俗称连通块)

割点:删去某个点后,连通块增加了,那么这个点就是割点

割边:删去某条边后,连通块增加了,那么这个点就是割边

点双:删掉一个点后,连通性不变的联通分量就是点双

割点

方法一:Floyd

枚举中转点不能走,再跑 Floyd 就好了

方法二:dfs爆搜

每个点 dfs 一下即可。

方法三:tarjan

如果对于一个点 \(u\),它至少存在一个儿子 \(v\) 不能回到比 \(u\) 更高的点,那么u就是割点
因为删掉 \(u\) 之后,\(v\) 所在的子树没有返祖边可以连回来。
我们可以用\(low\)\(dfn\)快速判断是否能连回来,\(low_v\ge dfn_u\) 就表示不能连回来,
只在非根节点时成立

代码:

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
const int MAXN = 1e5 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;

void init(){

  return;
}
int n,m;
vector<int>e[MAXN];
int dfn[MAXN],low[MAXN],times;
vector<int>ans;
void tarjan(int u,int fa){
  dfn[u] = low[u] = ++times;
  int son_cnt = 0;
  bool flag = false;
  for(auto v : e[u]){
    if(fa == v) continue;
    if(dfn[v]){
      low[u] = min(low[u],dfn[v]);
    }else{
      son_cnt++;
      tarjan(v,u);
      low[u] = min(low[u],low[v]);
      if(!flag && fa != u && low[v] >= dfn[u]){
        flag = 1;
        ans.push_back(u);
      }
    }
  }
  if(fa == u && son_cnt > 1)
    ans.push_back(u);
}
int main(){
  scanf("%d%d",&n,&m);
  for(int i = 1;i <= m;i++){
    int x,y;
    scanf("%d%d",&x,&y);
    e[x].push_back(y);
    e[y].push_back(x);
  }
  for(int i = 1;i <= n;i++){
    if(!dfn[i]){
      tarjan(i,i);
    }
  }
  sort(ans.begin(),ans.end());
  printf("%d\n",ans.size());
  for(auto y : ans)
    printf("%d ",y);
  return 0;
}

割边

如果对于一个点 \(u\),它至少存在一个儿子 \(v\) 不能回到比 \(u\) 更高的点,那么u就是割边
因为删掉 \(u\) 之后,\(v\) 所在的子树没有返祖边可以连回来。
我们可以用\(low\)\(dfn\)快速判断是否能连回来,\(low_v > dfn_u\) 就表示不能连回来,

代码:

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
const int MAXN = 1e5 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;

void init(){

  return;
}
int n,m;
vector<int>e[MAXN];
int dfn[MAXN],low[MAXN],times;
vector<pair<int,int>>ans;
bool cmp(const pair<int,int> &x,const pair<int,int> &y){
  if(x.first != y.first)
    return x.first < y.first;
  return x.second < y.second;
}
void tarjan(int u,int fa){
  dfn[u] = low[u] = ++times;
  for(auto v : e[u]){
    if(v == fa) continue;
    if(dfn[v]){
      low[u] = min(dfn[v],low[u]);
    }else{
      tarjan(v,u);
      low[u] = min(low[v],low[u]);
      if(low[v] > dfn[u]){
        ans.push_back({min(u,v),max(u,v)});
      }
    }
  }
}
int main(){
  scanf("%d%d",&n,&m);
  for(int i = 1;i <= m;i++){
    int x,y;
    scanf("%d%d",&x,&y);
    e[x].push_back(y);
    e[y].push_back(x);
  }
  for(int i = 1;i <= n;i++){
    if(!dfn[i])
      tarjan(i,i);
  }
  sort(ans.begin(),ans.end(),cmp);
  for(auto y : ans){
    printf("%d %d\n",y.first,y.second);
  }
  return 0;
}

点双

删去一个点后连通性不变的连通分量就是点双连通分量,简称点双。

  • 两个点双之间最多只有一个公共点,这个点是割点。
  • 对于一个点双,它在搜索树中dfn最小的点一定是割点或根节点

所以,我们用栈记录搜索过的点,在搜索到割点之后的回溯段,把点双统计出来即可。

代码:

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
const int MAXN = 5e5 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;

void init(){

  return;
}
int n,m;
vector<int>e[MAXN];
stack<int>st;
int dfn[MAXN],low[MAXN],times,deg[MAXN];
vector<vector<int>>ans;
void tarjan(int u,int fa){
  st.push(u);
  dfn[u] = low[u] = ++times;
  for(auto v : e[u]){
    if(fa == v) continue;
    if(dfn[v]){
      low[u] = min(low[u],dfn[v]);
    }else{
      tarjan(v,u);
      low[u] = min(low[u],low[v]);
      if(low[v] >= dfn[u]){
        // printf("???%d %d\n",u,v);
        vector<int>tmp{u};
        while(tmp.back() != v){
          tmp.push_back(st.top());
          deg[st.top()]++;
          st.pop();
        }
        deg[u]++;
        ans.push_back(tmp);
      }
    }
  } 
}
int main(){
  scanf("%d%d",&n,&m);
  for(int i = 1;i <= m;i++){
    int x,y;
    scanf("%d%d",&x,&y);
    e[x].push_back(y);
    e[y].push_back(x);
  }
  for(int i = 1;i <= n;i++){
    if(!dfn[i]){
      tarjan(i,i);
      // while(st.size()) st.pop();
    }
  }
  // for(int i = 1;i <= n;i++){
    // printf("%d ",dfn[i]);
  // }
  // printf("\n");
  // for(int i = 1;i <= n;i++){
    // printf("%d ",low[i]);
  // }
  // printf("\n");
  for(int i = 1;i <= n;i++){
    if(!deg[i]){
      ans.push_back(vector<int>(1,i));
    }
  }
  printf("%d\n",ans.size());
  for(int i = 0;i < ans.size();i++){
    printf("%d ",ans[i].size());
    for(auto y : ans[i]){
      printf("%d ",y);
    }
    printf("\n");
  }
  return 0;
}
posted @ 2025-08-19 08:33  Ruochen_xia  阅读(10)  评论(0)    收藏  举报