发布于  更新于 

POJ2914 Lutece 2710 Minimum Cut

图论 题解 OI

题意

个点 条边(最多完全图)的带权无向图,问至少删去的边权之和,使删边后图不连通。(任意两点的最小割)。

分析

显然枚举两点用最大流最小割来求跑不过。介绍此类问题的模板做法:Stoer-Wagner 算法,主要思想是枚举寻找任意两点的最小割,每处理两点之后进行“缩点”操作,可保证 复杂度。

考虑 的最小割把 分割为两侧,则枚举两点 一定属于一下两种情况:

  • 不在同一侧,那么枚举到 时就能求出答案。
  • 在同一侧,那么可以进行缩点操作,把 合并成一个点,不会影响答案。

只需要给定起点时能够 求出最小割,然后不断删掉(合并)一个点再进行 的枚举,最终可达到 的复杂度。

由于具有缩点的特性,可以考虑类似 Prim 的做法,不断向一个点集中加点,这个点集对应最终最小割方案的一侧,而最小割另一侧已经通过缩点过程被缩成了一个点,于是只需要用 w[i] 数组来维护每个点集外的点到点集的直接连边边权之和。不断地选取当前 w[i] 最大的点加入点集中,最终选取的点的 w[v] 值就对应一种合法、并且最优的‘单点’最小割方案。

此时即可统计答案,然后缩点,再进行下一次类 Prim 枚举。缩点时应该考虑将最终剩下的 (一定要合并 ,下一次从头枚举才是有意义的,否则找到的最终点还是 )和上一次添加进点集的点 进行合并,容易贪心证明此种合并方式的最优性。

为了合并操作的方便,使用邻接矩阵存图,可以用下面的 for 循环完成缩点:

1
2
3
4
5
for (int i = 1; i <= remain; i++) {
if (i == u || i == v) continue;
g[i][u] += g[i][v];
g[u][i] += g[v][i];
}

然后只需删除 ,使得之后不再枚举它。下文代码中利用 fa 数组删除 是一种巧妙的实现方式。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
int g[MAXN][MAXN];
int w[MAXN]; // w[i] 已选定集合中所有点到 i 的直接连边边权总和
int fa[MAXN]; // 处理合并操作后,删掉其中一个点
bool ins[MAXN];

int main() {
int n, m;
while (cin >> n >> m) {
memset(g, 0, sizeof g);
for (int i = 1; i <= m; i++) {
int u = read();
int v = read();
int c = read();
g[u][v] += c;
g[v][u] += c;
}
for (int i = 0; i < n; i++) {
fa[i] = i;
}
int ans = INF;
int remain = n;
while (remain > 1) {
memset(w, 0, sizeof w);
memset(ins, false, sizeof ins);
ins[0] = true;
int u = 0;
int v = 0;
for (int i = 1; i < remain; i++) {
u = v;
v = -1;
int cut_v = -1;
for (int j = 1; j < remain; j++) {
if (!ins[j]) {
w[j] += g[fa[j]][fa[u]];
if (w[j] > cut_v) {
cut_v = w[j];
v = j;
}
}
}
ins[v] = true;
}
ans = min(ans, w[v]);

// 类似并查集的合并操作
for (int i = 1; i < remain; i++) {
if (i == v) continue;
g[fa[i]][fa[0]] += g[fa[i]][fa[v]];
g[fa[0]][fa[i]] += g[fa[v]][fa[i]];
}
remain--;
fa[v] = fa[remain]; // 相当于是把 v 删掉了
}

write(ans);
putchar('\n');
}
}

Lutece 2710 是双倍经验。