冗余路径:https://www.acwing.com/problem/content/397/
无向图的边的双连通分量:理解1:极大的不包含桥的连通块(桥(割边):去掉这条边,图会被分割成俩个不连通的子图);理解2:任何两个点之间必定包含俩条不相交的路径(边的位置不相交,点可以相交,如果俩条路径分别包含(x,y),(y,x)俩个边,那么是相交的路径)。
有向图的强连通分量:任何两个点都可以互相到达。
题意:给一个无向连通图,问最少加几条边可以让这个图变成边的双连通图
题解:有向图的强连通分量:最少加几条边,可以让这个图变成,强连通图,p是入度为0的点,q是出度为0的点。
无向图的边双连通分量:最少加几条边,可以让这个图变成,边的双连通图,cnt是叶子节点的数量,也就是度数为1的点的数量。
#include <algorithm>
#include <bitset>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <functional>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
//#include <unordered_map>
//#include <unordered_set>
//#include <bits/stdc++.h>
//#define int long long
#define pb push_back
#define pii pair<int, int>
#define mpr make_pair
#define ms(a, b) memset((a), (b), sizeof(a))
#define x first
#define y second
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;
inline int read() {
char ch = getchar();
int s = 0, w = 1;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
s = s * 10 + ch - '0', ch = getchar();
}
return s * w;
}
const int N = 5010, M = 20010;
int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
int id[N], dcc_cnt;
bool is_bridge[M];
int d[N];
void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; }
void tarjan(int u, int from) {
dfn[u] = low[u] = ++timestamp;
stk[++top] = u;
for (int i = h[u]; ~i; 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]) {
//边的编号是0 1是一对,2 3是一对 4 5是一对
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);
}
}
signed main() {
n = read(), m = read();
memset(h, -1, sizeof(h));
while (m--) {
int a = read(), b = read();
add(a, b), add(b, a);
}
tarjan(1, -1);//将边的双连通分量找到,缩点
for (int i = 0; i < idx; i++) {
//枚举边的编号,看看是否是桥,是桥的话那就给它对应的两边的点所在的双连通分量度数+1
//因为i是桥的话,i^1一定也是桥,也就是这条无向边的两端的点对应的连通分量中间连一条边
if (is_bridge[i]) d[id[e[i]]]++;
}
int cnt = 0;
//度数为1,代表这个连通分量是叶子节点,记录数量
for (int i = 1; i <= dcc_cnt; i++) {
if (d[i] == 1) cnt++;
}
printf("%d\n", (cnt + 1) / 2);
return 0;
}