双连通分量
双连通分量是针对无向图来说的,双连通分量分为两种:点双连通, 边双连通。
割点:对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点。
桥:对于一个无向图,如果删掉一条边后图中的连通分量数增加了,则称这条边为桥或者割边。
点双连通:在一张连通的无向图中,对于两个点u和v,如果无论删去哪个点(只能删去一个,且不能删 u 和v 自己)都不能使它们不连通,我们就说u和v点双连通。
边双连通:在一张连通的无向图中,对于两个点u和v,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说u和v边双连通。
一、模板:
参考代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<set>
using namespace std;
const int N = 1e6 + 10;
int root;
int n, m;
int dfn[N], low[N], timetamp;
vector<int> g[N];
set<int> gd;
void tarjan(int u, int fa){
dfn[u] = low[u] = ++ timetamp;
int cnt = 0;
for(int i : g[u]){
if(!dfn[i]){
cnt ++;
tarjan(i, u);
low[u] = min(low[u], low[i]);
if(u != fa && low[i] >= dfn[u] && !gd.count(u)){
gd.insert(u);
}
}
else{
low[u] = min(low[u], dfn[i]);
}
}
if(u == fa && cnt >= 2 && !gd.count(u)){
gd.insert(u);
}
return ;
}
int main(){
scanf("%d%d",&n, &m);
for(int i = 0; i < m; i++){
int a, b;
scanf("%d%d",&a, &b);
g[a].push_back(b);
g[b].push_back(a);
}
for(root = 1; root <= n ; root ++){
if(!dfn[root]){
tarjan(root, root);
}
}
printf("%ld\n", gd.size());
for(auto i : gd){
printf("%d ", i);
}
printf("\n");
return 0;
}
二、应用
思路: 对原图求边双连通分量,再对缩点之后的图求直径即可。
参考代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<set>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 3e5 + 10;
vector<int> g[N], h[N];
int n, m;
int dfn[N], low[N], timetamp;
int start, en, ans;
int dist[N];
int id[N];
int scc_cnt, scc_size[N];
int stk[N], top;
bool st[N];
void tarjan(int u, int fa){
dfn[u] = low[u] = ++ timetamp;
stk[++ top] = u;
st[u] = true;
for(int i : g[u]){
//printf("****%d %d\n", i, u);
if(!dfn[i]){
tarjan(i, u);
low[u] = min(low[u], low[i]);
}
else if(i != fa){
low[u] = min(low[u], dfn[i]);
}
}
if(low[u] == dfn[u]){
int y;
++ scc_cnt;
do{
y = stk[top --];
st[y] = false;
id[y] = scc_cnt;
scc_size[scc_cnt] ++;
}while(y != u);
}
}
void dfs(int u, int f){
dist[u] = dist[f] + 1;
for(int i : h[u]){
if(i != f){
dfs(i, u);
}
}
}
signed main(){
scanf("%lld%lld",&n, &m);
for(int i = 0; i < m ; i ++){
int a, b;
scanf("%lld%lld",&a, &b);
g[a].push_back(b);
g[b].push_back(a);
}
tarjan(1, -1);
for(int i = 1; i <= n ; i ++){
for(int j : g[i]){
int a = id[i];
int b = id[j];
if(a != b){
h[a].push_back(b);
}
}
}
/*
printf("%lld\n", scc_cnt);
for(int i = 1; i <= n ; i ++){
printf("%lld ",id[i]);
}
printf("\n");
*/
dfs(1, 0);
for(int i = 1; i <= scc_cnt; i ++){
if(dist[i] > dist[start]){
start = i;
}
}
dfs(start, 0);
for(int i = 1; i <= scc_cnt; i ++){
ans = max(ans, dist[i] - 1);
}
printf("%lld\n", ans);
}
题意: 给定一张无向图,在其基础上添加一些无向边,使得每个点之间至少存在两条相互分离的路径。
思路:
首先对原图求边双连通分量, 做完后整张图转换为只剩下点和桥的树,树的叶子结点即需要添加边的点,当叶子个数为偶数时,直接将叶子两辆相连。当叶子个数为奇数时,两辆相连之后剩余的一个叶子连向树上任意一条边即可。综上所述答案为(cnt(叶子节点个数) + 1) / 2 。
参考代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 5010, M = 4e4 + 10;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timetamp;
int n, m;
int stk[N], top;
int d[N];
int id[N];
int dcc_cnt, dcc_size[N];
bool st[N], is_bridge[M];
void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
void tarjan(int u, int fa){
dfn[u] = low[u] = ++timetamp;
stk[++ top] = u;
st[u] = true;
for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if(!dfn[j]){
tarjan(j, i);
low[u] = min(low[u], low[j]);
if(dfn[u] < low[j]){
is_bridge[i] = is_bridge[i ^ 1] = true;
}
}
else if(i != (fa ^ 1)){
low[u] = min(low[u], dfn[j]);
}
}
if(low[u] == dfn[u]){
int y;
++dcc_cnt;
do{
y = stk[top --];
st[y] = false;
id[y] = dcc_cnt;
dcc_size[dcc_cnt] ++;
}while(y != u);
}
}
int main(){
scanf("%d%d",&n, &m);
memset(h, -1, sizeof(h));
for(int i = 0; i < m ; i ++){
int a, b;
scanf("%d%d",&a, &b);
add(a, b);
add(b, a);
}
tarjan(1, -1);
for(int i = 0; i < idx ; i ++){
if(is_bridge[i]){
d[id[e[i]]] ++;
}
}
int cnt = 0;
for(int i = 1; i <= dcc_cnt; i ++){
if(d[i] == 1){
cnt ++;
}
}
printf("%d\n",(cnt + 1) / 2);
}
很裸的点双连通,直接套模板即可。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 1e4 + 10;
int dfn[N], low[N], timetamp;
int root, ans;
int n, m;
vector<int> g[N];
void tarjan(int u){
dfn[u] = low[u] = ++ timetamp;
int cnt = 0;
for(int i : g[u]){
if(!dfn[i]){
tarjan(i);
low[u] = min(low[u], low[i]);
if(low[i] >= dfn[u]){
cnt ++;
}
}
else {
low[u] = min(low[u], dfn[i]);
}
}
if(u != root && cnt){
cnt ++;
}
ans = max(ans, cnt);
}
int main(){
while(~scanf("%d%d",&n, &m)){
if(n == 0 && m == 0){
break;
}
memset(dfn, 0, sizeof(dfn));
timetamp = ans = 0;
int cnt = 0;
for(int i = 0; i < m ; i ++){
int a, b;
scanf("%d%d",&a, &b);
g[a].push_back(b);
g[b].push_back(a);
}
for(root = 0; root < n ; root ++){
if(!dfn[root]){
cnt ++;
tarjan(root);
}
}
for(int i = 0; i < n; i ++){
g[i].clear();
}
printf("%d\n", ans + cnt - 1);
}
return 0;
}