首页 > 其他分享 >桥、割点和边双连通分量

桥、割点和边双连通分量

时间:2024-04-16 17:35:55浏览次数:18  
标签:连通 int 割点 dfs back dfn MAXN low 分量

定义:删除后会增加联通块数量的边被称作

那么,如何求解呢?

方法一

首先跑出一颗dfs树。比如下图(\(2-6,1-5\) 的边是非树边):

可以发现,所有非树边和其构成的环上的所有边不可能是桥,因为删去后仍可以通过环的另一半。比如上图中只有 \(1-2\) 一个桥。那是不是除了这些边以外都是桥呢?很明显是的,因为只存在这一条边连接它的子树和父亲子树。

所以我们可以找出所有非树边,然后求出它两个端点的 LCA,再使用树上差分求解。

但这样还是太麻烦了,所以我们可以用到dfs树的一个性质:所有非树边的两个端点在树上一定有祖先关系。这样我们就不用写 LCA 了。

时空复杂度均为 \(O(N+M)\)。

代码

#include<bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;

const int MAXN = 200001, MAXM = MAXN;

int n, m, dep[MAXN], s[MAXN];
vector<pii> e[MAXN];
vector<int> ans;

void dfs(int u, int f) {
  for(auto [v, id] : e[u]) {
    if(!dep[v]) {
      dep[v] = dep[u] + 1;
      dfs(v, id);
      s[u] += s[v];
    }else if(dep[v] < dep[u] && f != id) {
      s[u]++, s[v]--;
    }
  }
  if(f && !s[u]) {
    ans.push_back(f);
  }
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for(int i = 1, u, v; i <= m; ++i) {
    cin >> u >> v;
    e[u].push_back({v, i});
    e[v].push_back({u, i});
  }
  for(int i = 1; i <= n; ++i) {
    if(!dep[i]) {
      dep[i] = 1;
      dfs(i, 0);
    }
  }
  cout << ans.size() << "\n";
  for(int x : ans) {
    cout << x << " ";
  }
  return 0;
}

方法二

同样的,先跑出一颗dfs树。

我们令 \(dfn_u\) 表示 \(u\) 的时间戳,\(low_u\) 为 \(u\) 的子树内所有节点中,通过非树边能够抵达的结点之中的 \(\min \{dfn_v\}\)。

设树上结点 \(u\) 的父亲是 \(v\),如果 \(low_u\) 大于 \(dfn_v\),说明不存在 \(u\) 的子树到 \(u\) 的祖先的非树边,则这条边就是桥。

时空复杂度均为 \(O(N+M)\)。

代码

#include<bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;

const int MAXN = 200001, MAXM = MAXN;

int n, m, dfn[MAXN], low[MAXN], tot;
vector<pii> e[MAXN];
vector<int> ans;

void dfs(int u, int f) {
  dfn[u] = low[u] = ++tot;
  for(auto [v, id] : e[u]) {
    if(!dfn[v]) {
      dfs(v, id);
      low[u] = min(low[u], low[v]);
    }else if(dfn[v] < dfn[u] && f != id) {
      low[u] = min(low[u], dfn[v]);
    }
  }
  if(f && dfn[u] == low[u]) {
    ans.push_back(f);
  }
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for(int i = 1, u, v; i <= m; ++i) {
    cin >> u >> v;
    e[u].push_back({v, i});
    e[v].push_back({u, i});
  }
  dfs(1, 0);
  cout << ans.size() << "\n";
  for(int x : ans) {
    cout << x << " ";
  }
  return 0;
}

割点

定义:删除后会增加联通块数量的点被称作割点

用同样的方式思考:先跑出一颗dfs树,比如下图。

这张图中只有 \(7\) 是割点。

可以想到,如果一个点 \(u\) 不为根节点,且它的所有儿子至少有一个没有通往 \(u\) 的祖先的非树边,即 \(dfn_u > low_v\),则代表删除 \(u\) 后该儿子会独立,即 \(u\) 是割点。

时空复杂度均为 \(O(N+M)\)。

代码

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

const int MAXN = 200001, MAXM = MAXN;

int n, m, dfn[MAXN], low[MAXN], tot;
vector<int> e[MAXN], ans;

void dfs(int u, int fa) {
  dfn[u] = low[u] = ++tot;
  int cnt = 0;
  bool op = 1;
  for(int v : e[u]) {
    if(v != fa) {
      if(!dfn[v]) {
        cnt++;
        dfs(v, u);
        op &= (low[v] < dfn[u]);
        low[u] = min(low[u], low[v]);
      }else if(dfn[v] < dfn[u]) {
        low[u] = min(low[u], dfn[v]);
      }
    }
  }
  if((!fa && cnt > 1) || (fa && !op)) {
    ans.push_back(u);
  }
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for(int i = 1, u, v; i <= m; ++i) {
    cin >> u >> v;
    e[u].push_back(v);
    e[v].push_back(u);
  }
  for(int i = 1; i <= n; ++i) {
    if(!dfn[i]) {
      dfs(i, 0);
    }
  }
  sort(ans.begin(), ans.end());
  cout << ans.size() << "\n";
  for(int x : ans) {
    cout << x << " ";
  }
  return 0;
}

边双连通分量

定义:一个任意两个结点 \(u,v\) 均可在删除任意一条边后仍然可以互相到达极大子图被称为边双连通分量,即没有桥的极大子图。

方法很简单,只需把所有桥删去后看有多少个联通块即可

时空复杂度均为 \(O(N+M)\)。

代码

#include<bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;

const int MAXN = 200001, MAXM = 200001;

int n, m, dfn[MAXN], low[MAXN], tot, cnt, top;
bool flag[MAXM], vis[MAXN];
vector<pii> e[MAXN];
vector<int> ans[MAXN];

void dfs(int u, int f) {
  dfn[u] = low[u] = ++tot;
  for(auto [v, id] : e[u]) {
    if(!dfn[v]) {
      dfs(v, id);
      low[u] = min(low[u], low[v]);
    }else if(dfn[v] < dfn[u] && f != id) {
      low[u] = min(low[u], dfn[v]);
    }
  }
  if(f && dfn[u] == low[u]) {
    flag[f] = 1;
  }
}

void DFS(int u) {
  if(vis[u]) {
    return;
  }
  vis[u] = 1;
  ans[top].push_back(u);
  for(auto [v, id] : e[u]) {
    if(!flag[id]) {
      DFS(v);
    }
  }
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for(int i = 1, u, v; i <= m; ++i) {
    cin >> u >> v;
    e[u].push_back({v, i});
    e[v].push_back({u, i});
  }
  for(int i = 1; i <= n; ++i) {
    if(!dfn[i]) {
      dfs(i, 0);
    }
  }
  for(int i = 1; i <= n; ++i) {
    if(!vis[i]) {
      top++;
      DFS(i);
    }
  }
  cout << top << "\n";
  for(int i = 1; i <= top; ++i) {
    cout << ans[i].size() << " ";
    for(int x : ans[i]) {
      cout << x << " \n"[x == ans[i].back()];
    }
  }
  return 0;
}

标签:连通,int,割点,dfs,back,dfn,MAXN,low,分量
From: https://www.cnblogs.com/yaosicheng124/p/18138754

相关文章

  • 强连通分量、缩点
    强连通分量定义:强连通分量是指一个任意两点都可互相到达的极大子图。求解思路和桥、割点和边双连通分量很类似。首先跑出一颗dfs树,令\(dfn_u\)表示\(u\)的时间戳,\(low_u\)表示\(u\)的子树中仅通过非树边能到达的\(\min\{dfn_v\}\)。比如下图:在这张图中,黑色边为树边......
  • Tarjan 求双连通分量(点双连通分量、边双连通分量)
    注意:本文只针对无向图。对于无向图,显然不能只考虑简单的连通关系,应该研究一些更强的连通关系:双连通。前置芝士点双连通分量:若一个连通分量任意两点间都存在至少两条不经过(除起点和终点外)相同点的路径,我们就称这个连通分量为点双连通分量。边双连通分量:同理,若一个连通分量......
  • 割点
    割点割点是如果删除这个点,连通块+1的点就是割点。可以发现,在DFS树上,如果一个点的儿子不能通过一些非树边到达它的父亲,如果把它删除,这个儿子将会和他的父亲不连通。#include<bits/stdc++.h>usingnamespacestd;constintN=2e5+5;intlow[N],cnt,dfn[N......
  • 连通性+ 1
    早在普及组的时候,我们就学会了:DFS(BFS)搜连通块并查集在加边的情况下动态维护连通块(支持离线处理删边)现在,我问你:我删去一个点/边,判断剩下的图存在原本某两个连通的点现在不连通?我随机删去一条边,判断剩下的图中某两个点是否一定连通?我随机给你一些点,判断其中两两是......
  • 边双连通分量
    边双连通分量我们令双连通表示两个节点之间段开一条边还是连通的。边双连通分量表示求出几个最大的点集,使得任意一个点集中点两两双连通。例题luoguP8436方法1我们发现,如果两个节点原先连通但不是双连通,肯定他们之间的路径是经过某一座桥,所以可以求出来桥,把桥拆......
  • 聚水潭与金蝶云星空对接集成退货退款查询连通[聚水潭][销售退货单标准新增]-v1(聚水潭
    聚水潭与金蝶云星空对接集成退货退款查询连通[聚水潭][销售退货单标准新增]-v1(聚水潭--销售退货对接-P-12495392-这个店铺的数据)接入系统:聚水潭聚水潭是SaaS协同平台、电商ERP软件。聚水潭成立于2014年,创始人兼CEO骆海东拥有近三十年传统及电商ERP的研发和实施部署经验。......
  • [转]Docker 两个不同网络间实现连通
    原文地址:Docker两个不同网络间实现连通-西瓜君~-博客园一、启动不同网络的容器1、启动两个bridge(自带默认)桥接的容器[root@yang~]#dockerrun-it--nametomcat1tomcat[root@yang~]#dockerrun-it--nametomcat2tomcat#查看容器[root@yang~]#dockerps......
  • 图论割点模板
    #include<iostream>#include<vector>#include<algorithm>#include<math.h>#include<sstream>#include<string>#include<string.h>#include<iomanip>#include<stdlib.h>#include<map>#include<queue......
  • Python环境下一种改进小波分解方法-用于多分量信号的分解
    小波通俗的讲就是一种振幅表现为在正负之间震荡的波形。小波变换在基于短时傅立叶变换的前提下,又加入了其所没有的可随频率变化的“时间-频率”窗口,其能对时间、频率进行局部化分析,并且对待处理信号通过多尺度处理使其表现为时-频细分的特点,是一种能突出信号时频特点以及细节的......
  • 强连通分量随记随忘
    vis用于判断某个点是否在栈中tot表示强连通分量的数量belong[x]表示点x所属的强连通分量all[]与tot变量相关,表示此强连通分量的点的数量outd[]与tot变量相关,表示此强连通分量的出度模板代码如下:#include<bits/stdc++.h>usingnamespacestd;constintN=1e4+5;intn,m,......