题意:
给出一个有桥的图,问最少还要增加多少条边才能使得这个图没有割边。
思路:先求出来割边,直接上Tarjan就行,然后把所有的双连通分量都缩成一个点,把图变成一颗树,找到叶子节点的个数ans,然后在叶子节点上两两之间加一条边(如果ans是奇数,从最后剩下的节点引出一条边随便连在其他的叶子节点上就行了)所以答案就是(ans + 1) / 2.
至于具体怎么两两之间加边,引用一下 这位博主的一个思路,如下:
统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 4 * 1e4 + 100;
int n, m, tot, num;
int head[maxn], dfn[maxn], low[maxn], pre[maxn], degree[maxn];
bool bridge[maxn]; //标记这条边是否为割边
struct node
{
int u, v, next;
}edge[maxn];
inline void Init() { //初始化
tot = 2;
num = 0;
memset(head, -1, sizeof(head));
memset(dfn, 0, sizeof(dfn));
memset(bridge, false, sizeof(bridge));
memset(degree, 0, sizeof(degree));
}
inline void add(int u, int v) {
edge[tot].u = u;
edge[tot].v = v;
edge[tot].next = head[u];
head[u] = tot++;
}
void Tarjan(int u, int edge_i) //求割边
{
dfn[u] = low[u] = ++num;
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].v;
if(!dfn[v])
{
Tarjan(v, i);
low[u] = min(low[u], low[v]);
if(low[v] > dfn[u])
{
bridge[i] = bridge[i^1] = true;
}
}
else if(i != (edge_i^1))
{
low[u] = min(low[u], dfn[v]);
}
}
}
int finds(int x) //用并查集来缩点
{
return pre[x] == x ? x : pre[x] = finds(pre[x]);
}
int main()
{
//freopen("in.txt", "r", stdin);
cin >> n >> m;
Init();
int u, v;
for(int i = 1; i <= m; ++ i)
{
scanf("%d%d", &u, &v);
add(u, v);
add(v, u);
}
for(int i = 1; i <= n; ++ i) //为了防止这个图不是连通图,在本题中应该所有样例都是连通图
{
if(!dfn[i])
{
Tarjan(i, 0);
}
}
for(int i = 1; i <= n; ++ i) { //并查集初始化
pre[i] = i;
}
for(int i = 2; i < tot; i += 2) { //如果这条边不是割边就合并两个端点
if(!bridge[i]) {
pre[finds(edge[i].u)] = finds(edge[i].v);
}
}
for(int i = 2; i < tot; i += 2) { //统计每个缩点之后的节点的度
if(bridge[i]) {
degree[finds(edge[i].u)]++;
degree[finds(edge[i].v)]++;
}
}
int ans = 0;
for(int i = 1; i <= n; ++ i)
{
if(degree[i] == 1 && finds(i) == i) //只有度为1的才是叶子节点
{
ans++;
}
}
ans = (ans + 1) / 2;
cout << ans << endl;
return 0;
}