
一个无向图是边双连通分量的充分必要条件:对于无向图中所有不同的顶点u,v,u,v之间至少存在两条互相分离的路径
根据题意,就是求最少加几条边,能使原来的连通图变成一个双连通分量
可以证明,将原本已经是边连通分量的连通块缩点后,所有点都以桥连接(可以看成一棵树),将所有度数为1的点(叶子结点)两两相连所需要的边数即为答案
#include<bits/stdc++.h>
using namespace std;
#define fr first
#define se second
#define et0 exit(0);
#define rep(i, a, b) for(int i = (int)(a); i <= (int)(b); i ++)
#define rrep(i, a, b) for(int i = (int)(a); i >= (int)(b); i --)
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef unsigned long long ULL;
const int INF = 0X3f3f3f3f, N = 1e4 + 10, M = 2e4 + 10, MOD = 1e9 + 7;
const double eps = 1e-7, PI = 3.1415926;
int n, m;
int head[N], idx;
struct EGDE {
int to, next;
} eg[N];
void add(int x, int y) {
eg[idx].to = y;
eg[idx].next = head[x];
head[x] = idx++;
}
int dfn[N], low[N], timestamp;
int stk[N], top;
int id[N], dcc_cnt;
int d[N];
bool is_bridge[M];
void tarjan(int u, int from) {
dfn[u] = low[u] = ++ timestamp;
stk[ ++ top] = u;
for (int i = head[u]; ~i; i = eg[i].next) {
int j = eg[i].to;
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 != (from ^ 1))
low[u] = min(low[u], dfn[j]);
}
if (dfn[u] == low[u]) {
++ dcc_cnt;
int y;
do {
y = stk[top -- ];
id[y] = dcc_cnt;
} while (y != u);
}
}
void Count_bridge() {
for (int i = 0; i < idx; i ++ )
if (is_bridge[i])
d[id[eg[i].to]] ++ ;
}
void work() {
memset(head, -1, sizeof head);
cin >> n >> m;
rep(i, 1, m) {
int x, y;
cin >> x >> y;
add(x, y);
add(y, x);
}
tarjan(1, -1);
Count_bridge();
int res = 0;
rep(i, 1, dcc_cnt)
if (d[i] == 1) res++;
cout << (res + 1) / 2 << endl;
}
signed main() {
int test = 1;
// cin >> test;
while (test--) {
work();
}
return 0;
}

割点:对于一个连通图中的点x,假如删去这个点以及与所有x相连的边之后图不连通,那么称x为该图的割点。
所以当且仅当被删除的点为割点时,连通块的数量才会增加
根据题意,只要统计连通块个数,并判断每个顶点是否为割点并统计删除后增加的连通块的最大值即可
#include<bits/stdc++.h>
using namespace std;
#define fr first
#define se second
#define et0 exit(0);
#define rep(i, a, b) for(int i = (int)(a); i <= (int)(b); i ++)
#define rrep(i, a, b) for(int i = (int)(a); i >= (int)(b); i --)
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef unsigned long long ULL;
const int INF = 0X3f3f3f3f, N = 1e5 + 10, M = 2e5 + 10, MOD = 1e9 + 7;
const double eps = 1e-7, PI = 3.1415926;
int n, m;
int head[N], idx;
struct EGDE {
int to, next;
} eg[M];
void add(int x, int y) {
eg[idx].to = y;
eg[idx].next = head[x];
head[x] = idx++;
}
int dfn[N], low[N], timestamp;
int ans;
void tarjan(int u, int root) {
dfn[u] = low[u] = ++ timestamp; //时间戳
int cnt = 0; // 记录u下面有几个连通块
for (int i = head[u]; ~i; i = eg[i].next) {
int j = eg[i].to;
if (!dfn[j]) {
tarjan(j, root);
low[u] = min(low[u], low[j]);
if (dfn[u] <= low[j])cnt ++; //如果满足条件那么u是割点,记录u下面连接几个连通块
} else low[u] = min(low[u], dfn[j]); // 更新low[u];
}
if (root != u)cnt ++; // 如果u不是根节点 再把u的父节点的连通块加上
ans = max(ans, cnt); // 最后取一个最大值
}
void work() {
while (cin >> n >> m, n || m) {
idx = ans = timestamp = 0;
memset(head, -1, sizeof head);
memset(dfn, 0, sizeof dfn);
rep(i, 1, m) {
int x, y;
cin >> x >> y;
add(x, y);
add(y, x);
}
int cnt = 0;
rep(i, 0, n - 1)
if (!dfn[i]) tarjan(i, i), cnt++;
cout << ans + cnt - 1 << endl;
}
}
signed main() {
int test = 1;
// cin >> test;
while (test--) {
work();
}
return 0;
}
除了acwing之外的参考: