\(AcWing\) \(1183\) 电力
一、题目描述
给定一个由 \(n\) 个点 \(m\) 条边构成的 无向图 ,请你求出该图 删除一个点 之后,连通块最多有多少。
输入格式
输入包含多组数据。
每组数据第一行包含两个整数 \(n,m\)。
接下来 \(m\) 行,每行包含两个整数 \(a,b\),表示 \(a,b\) 两点之间有边连接。
数据保证无重边。
点的编号从 \(0\) 到 \(n−1\)。
读入以一行 \(0\) \(0\) 结束。
输出格式
每组数据输出一个结果,占一行,表示连通块的最大数量。
二、核心思路
-
① 图可能不是完全连通的,需要 遍历 原图连通块个数\(cnt\)
注意:这个连通块与点双什么的没有关系,是指 聚合在一起 和 分离 存在的块的意思。我才不告诉你为什么我理解的这么深入~ -
每个连通块内找下是否存在 割点
- 如果存在,尝试下删除它后会产生多少个新的连通块数\(S\),就能增加 \(S\) 个连通块
- 如果不存在,那么没有可以删除的,换句话说,删除哪个节点,最终还是原来那些连通块
- 在通过割点求连通块时,需要 特判根节点,不是根节点,还需要加上通往根节点那条边指向的连通块,即\(S+1\)
-
原来有\(cnt\)个,现在你删除一个割点导致增加了\(2\)个独立块的话,其实是\(1->2\),需要把原来的\(1\)再去掉,即\(cnt+S-1\)就是答案
三、实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = 300010;
//链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int ans; //在处理每个连通块时,尝试删除每一个割点,记录可以生成的孤立块的数量最大值
int dfn[N], low[N], stk[N], timestamp, top, root;
//点双连通分量 模板
vector<int> bcc[N];
int bcnt;
void tarjan(int u, int fa) {
int cnt = 0; // 去掉u之后还剩多少个连通块
low[u] = dfn[u] = ++timestamp;
stk[++top] = u;
int son = 0;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa) continue;
if (!dfn[j]) {
son++;
tarjan(j, u);
low[u] = min(low[u], low[j]);
if (low[j] >= dfn[u]) {
cnt++; //把u删除掉,所有的j就都孤立了,多出来1个孤立块
int x;
bcnt++;
do {
x = stk[top--];
bcc[bcnt].push_back(x);
} while (x != j); //将子树出栈
bcc[bcnt].push_back(u); //把割点/树根也丢到点双里
}
}
low[u] = min(low[u], dfn[j]);
}
//特判独立点
if (fa == -1 && son == 0) bcc[++bcnt].push_back(u);
if (u != root) cnt++; // u不是根节点的话,出了自己的子树,还有他的父节点也断开了连接
ans = max(ans, cnt); //不断更新生成的孤立块最大数量
}
void solve(int n, int m) {
memset(dfn, 0, sizeof dfn);
memset(h, -1, sizeof h);
idx = timestamp = 0;
while (m--) {
int a, b;
scanf("%d %d", &a, &b);
a++, b++; //本题点号从0开始,+1后平移到1开始 0≤a,b<n
add(a, b), add(b, a);
}
//连通块的最大(多)数量
ans = 0;
int cnt = 0; //原图中块数量(互相孤立的有多少个块)
for (root = 1; root <= n; root++)
if (!dfn[root]) {
tarjan(root, -1);
cnt++; //记录有多少个独立的块
}
//按点双缩点后是一棵树,这个是关键
/*(1) 原来就有cnt个,这个是底
(2) 尝试枚举每个块,在块里找割点
① 有割点,删除此点会产生更多的独立块:
I.割点是根,连接着S个点,增加S个独立块
II.割点不是根,连接着S个点,还要加上向上的父节点所属的独立块
② 没有割点,随便删除哪个点都没用,不会产生新的独立块
*/
printf("%d\n", cnt + ans - 1);
}
int main() {
int n, m;
while (scanf("%d %d", &n, &m), n || m)
solve(n, m);
return 0;
}