题目链接:https://www.luogu.com.cn/problem/U640030
题目大意:
给你一个 \(n\) 个顶点 \(m\) 条边的无向图。顶点编号从 \(1\) 到 \(n\)。
请你求出该图删除一个点之后,连通块最多有多少。
解题思路:
首先,有两个比较容易被坑到的地方。
坑1:
虽然题目说不会存在重边,但是题目中可能会存在自环。而自环对答案没有影响,所以我们可以忽略掉所有的自环。
坑2:
如果一个连通块的大小为 \(1\)(即连通块中只有一个点),则删掉这个点,连通块的数量会减少 \(1\)。
我们肯定不希望删掉这样的点。但是如果题目中全都是这样的点,我们就不得不删除一个,使得连通块的个数为 \(n-1\)。
这种情况何时出现呢?就是在忽略掉自环后,图中的边数为 \(0\) 时,此时每个点都是一个连通块,答案为 \(n-1\)。因为你必须要删除掉一个点。
接着我们考虑每一个大小 \(\gt 1\) 的连通块,对于这样的连通块:
- 如果删除的是非割点,则连通块数量没有增加;
- 如果删除割点 \(u\),如果割点 \(u\) 同时处于 \(k\) 个点双连通分量重,则这 \(1\) 个连通块在删掉这个割点后会变成 \(k-1\) 个连通块,连通块数量增加 \(k-1\) 个。
所以,我们可以求出 \(mx\),\(mx\) 表示所有割点中处在点双连通分量中最多的那个割点对应的点双连通分量数。
答案为 \(cnt + mx - 1\)。
其中,\(cnt\) 表示一开始的连通块数。
注:如果不存在割点,但存在大小大于 \(1\) 的连通块,则答案为 \(cnt\)。
示例程序:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 5;vector<int> g[maxn];
int n, m, dfn[maxn], low[maxn], ts, cnt, mx, rt;void init() {for (int i = 1; i <= n; i++) {g[i].clear();dfn[i] = low[i] = 0;}ts = cnt = mx = 0;
}void tarjan(int u, int p) {dfn[u] = low[u] = ++ts;int son_cnt = 0;for (auto v : g[u]) {if (v == p) continue;if (!dfn[v]) {tarjan(v, u);low[u] = min(low[u], low[v]);if (low[v] >= dfn[u])son_cnt++;}else low[u] = min(low[u], dfn[v]);}if (son_cnt >= 2 || u != rt && son_cnt == 1) {mx = max(mx, son_cnt + (u != rt));}
}int main() {while (~scanf("%d%d", &n, &m) && n) {init();int mm = 0;for (int i = 0, u, v; i < m; i++) {scanf("%d%d", &u, &v);if (u == v) continue;mm++;g[u].push_back(v);g[v].push_back(u);}if (mm == 0) {printf("%d\n", n - 1);continue;}bool flag = false;for (int i = 1; i <= n; i++) {if (!dfn[i]) {if (g[i].size() == 0)flag = true;cnt++, rt = i, tarjan(i, -1);}}int ans = cnt + max(0, mx - 1);printf("%d\n", ans);}return 0;
}