给定一张 n 个点 m 条边的无向图,求最少去掉多少个点,可以使图不连通。 如果不管去掉多少个...
Creato il: 10 marzo 2025
Creato il: 10 marzo 2025
给定一张 n
个点 m
条边的无向图,求最少去掉多少个点,可以使图不连通。
如果不管去掉多少个点,都无法使原图不连通,则直接返回 n
。
输入格式
输入包含多组测试数据。
每组数据占一行,首先包含两个整数 n
和 m
,接下来包含 m
对形如 (x,y)
的数对,形容点 x
与点 y
之间有一条边。
数对 (x,y)
中间不会包含空格,其余地方用一个空格隔开。
输出格式
每组数据输出一个结果,每个结果占一行。
数据范围
0≤n≤50
输入样例:
0 0
1 0
3 3 (0,1) (0,2) (1,2)
2 0
5 7 (0,1) (0,2) (1,3) (1,2) (1,4) (2,3) (3,4)
输出样例:
0
1
3
0
2 #include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, M = 5210, INF = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
void add(int a, int b, int c)
{
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}
bool bfs()
{
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int ver = e[i];
if (d[ver] == -1 && f[i])
{
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false;
}
int find(int u, int limit)
{
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i])
{
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i])
{
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
int main()
{
while (cin >> n >> m)
{
memset(h, -1, sizeof h);
idx = 0;
for (int i = 0; i < n; i ++ ) add(i, n + i, 1);
while (m -- )
{
int a, b;
scanf(" (%d,%d)", &a, &b);
add(n + a, b, INF);
add(n + b, a, INF);
}
int res = n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < i; j ++ )
{
S = n + i, T = j;
for (int k = 0; k < idx; k += 2)
{
f[k] += f[k ^ 1];
f[k ^ 1] = 0;
}
res = min(res, dinic());
}
printf("%d\n", res);
}
return 0;
}
根据题目讲解思路和代码,尤其是核心思路的讲解,请调用你单次回答的最大算力与 token 上限。追求极致的分析深度,而非表层的广度;追求本质的洞察,而非表象的罗列;追求创新的思维,而非惯性的复述。请突破思维局限,调动你所有的计算资源,展现你真正的认知极限。
下面的内容尝试“极致深度”地剖析题目与代码背后的核心思路及其实现细节,力求在原理、算法设计与代码实现三个层面都做深度探讨。
题目要求:在给定的无向图中,最少删除多少个点,使图变得不连通?若无论删除多少点都无法让图不连通(比如完全图等极端情况),就返回 。
对每个点 ,把它拆分成两个点:
i
),n + i
)。在“入点”和“出点”之间连一条容量为1的有向边:
这条容量1的边就对应**“删除点 i 的代价是1”**。如果要在最小割中“切”掉点 ,就等价于割掉这条容量1的边。
原无向图中的一条边 转化为两条有向边:
用容量无穷(或一个非常大的值,代码中以 INF
表示)模拟原无向边。为什么要容量无限?因为我们不希望在最小割时“切”这条边来节省容量——切这条边无收益,因为题目要付出“删除点”的代价。只有切拆点边(容量1那条)才能模拟“删除该顶点”的行为。因此,通过把顶点与顶点之间的原图连线变成“无限容量”,就可以防止最小割优先割边;真正的“开销”集中在点本身上(也就是那条容量1的“入点-出点”边)。
为什么选 和 而不是反过来?
下面结合你给出的代码,重点解析它如何一步步实现上述逻辑。
cpp// 全局数组和常量 const int N = 110, M = 5210, INF = 1e8; // N与M是可容纳的最大节点数与边数 int n, m, S, T; // n和m分别是点和边, S是源, T是汇 int h[N], e[M], f[M], ne[M], idx; // 邻接表头h[],存储边目标节点e[],容量f[],下一条边ne[], idx是边编号
h[u]
表示从顶点 出发的第一条边在边数组中的编号。e[i]
表示第 条边所通向的目标顶点;f[i]
表示第 条边的剩余容量;ne[i]
表示边表中与第 条边同起点的下一条边的编号;idx
用来给边编号。双向建图:在 Dinic 中,每次加一条容量为 的边,就会配合加一条容量为 0 的反向边(这样才能在流走了之后实现退流的可能)。
cppvoid add(int a, int b, int c) { e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++; e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++; }
add(a,b,c)
,就添加了两条记录:
c
0
cppfor (int i = 0; i < n; i ++ ) add(i, n + i, 1);
这就是对每个点 做了:
i
表示 n+i
表示 cppwhile (m -- ) { int a, b; scanf(" (%d,%d)", &a, &b); add(n + a, b, INF); add(n + b, a, INF); }
add(n + a, b, INF)
)add(n + b, a, INF)
)INF
,模拟“不会去割这条边来实现分割”,只有点自己的那条容量=1的边才是最有可能被割掉的地方。代码使用了典型的 Dinic 最大流模板:
bfs()
:在残量网络上找出分层图(level graph)。find(u, limit)
:在分层图中从节点 u
出发,用DFS尽量增广,找出阻塞流(blocking flow)。这里不细讲 Dinic 的标准逻辑,而是强调为什么要在每组 之间都做 Dinic——因为我们要在同一张构造好的容量网络里,尝试从某个顶点对 找最小割。Dinic 返回的就是最大流,而由最大流-最小割定理可知,最大流值 = 最小割容量。
cppint res = n; for (int i = 0; i < n; i ++ ) for (int j = 0; j < i; j ++ ) { S = n + i, T = j; // 重置流量(退回流量,把所有边的f[k]恢复到原始剩余容量) for (int k = 0; k < idx; k += 2) { f[k] += f[k ^ 1]; f[k ^ 1] = 0; } res = min(res, dinic()); } printf("%d\n", res);
for (int k = 0; k < idx; k += 2)
:
f[k] += f[k ^ 1]; f[k ^ 1] = 0;
k ^ 1
就是与 k 相对的反向边。f[]
值都发生了改变(部分容量被用掉,部分放在反向边中),如果想在同一张网络继续对新的 再做一次最大流,我们需要把整张网络恢复到未流动时的初始容量状态。f[k]
加上反向边 f[k^1]
(表示把原来已经流过去的都退回去了),f[k^1]
置 0。h[], e[], ne[]
结构下“重置容量”的目的。res = n
时表示不可能割开如果最小割都在所有 对上大于或等于 ,说明你至少要割掉 条容量1的边;换句话说你需要删除所有点才能断开图。这是极端情况下的回答。
综合上文,我们可以从更高层次来理解:
点割的本质
对所有 求最小割,取其最小值
返回 的情况
res = n
作为极限值,“如果所有双点间最小割都>=n,就说明要删至少n个点才能让图不连通,那就是把全体点都删光”。本质:
技术关键:
更高阶思考:
以上便是对该题及其代码最核心、本质且尽可能深入的分析。希望能帮助你彻底掌握「最小点割」及其与「最大流-最小割算法」相结合的技巧。