原题:P1345 [USACO5.4] 奶牛的电信 Telecowmunication
题面:
P1345 [USACO5.4] 奶牛的电信 Telecowmunication
题目描述
农夫约翰的奶牛们喜欢通过电邮保持联系,于是她们建立了一个奶牛电脑网络,以便互相交流。这些机器用如下的方式发送电邮:如果存在一个由 台电脑组成的序列 ,且 与 相连, 与 相连,等等。那么电脑 和 就可以互发电邮。
很不幸,有时候奶牛会不小心踩到电脑上,农夫约翰的车也可能碾过电脑,这台倒霉的电脑就会坏掉。这意味着这台电脑不能再发送电邮了,于是与这台电脑相关的连接也就不可用了。
有两头奶牛就想:如果我们两个不能互发电邮,至少需要坏掉多少台电脑呢?请注意, 不能被破坏。请编写一个程序为她们计算这个最小值。
以如下网络为例:
1*
/
3 - 2*
这张图画的是有 条连接的 台电脑。我们想要在电脑 和 之间传送信息。电脑 与 , 与 直接连通。如果电脑 坏了,电脑 与 便不能互发信息了。
输入格式
第一行:四个由空格分隔的整数:。 是电脑总数,电脑由 到 编号。 是电脑之间连接的总数。后面的两个整数 和 是上述两头奶牛使用的电脑编号。连接没有重复且均为双向的(即如果 与 相连,那么 与 也相连)。两台电脑之间至多有一条连接。电脑 和 不会直接相连。
第 到 行:接下来的 行中,每行包含两台直接相连的电脑的编号。
输出格式
一行,一个整数,表示使电脑 和 不能互相通信需要坏掉的电脑数目的最小值。
输入输出样例 #1
输入 #1
3 2 1 2
1 3
2 3
输出 #1
1
说明/提示
对于 的数据:,。
刚学完最大流,所以找几道练习题来做。
分析一下题目,要求我们删除最少的点,以切断 间的连通性。来想一下做法。
首先看这个问题的性质,是不是有点像割点?用 来求割点可以吗?实际上是不行的,因为这道题中的图可能并不存在割点,需要删去多个点以切断 间的连通性,就算存在,也不能保证 处于不同的由这个割点分割出的子图。所以求割点在这题是没什么效果的。
那么回顾我们刚刚学过的最大流,由最大流最小割定理可得,最大流等于最小割,而最小割是什么?就是将整个图分成两个集合 分别包含源点 和汇点 ,取边集 代表连接这两个点集的边的集合,让边集的边容量和最小即为最小割。
所以我们可以考虑使用求最小割的方法来求解这道题。但问题在于,这道题要求我们删点,而最小割求的是边容量的最小和,如何将删点转化为删边呢?此时就要用到一个经典的处理方式,我们将每个点一分为二,分别记为进入的点和出去的点,即 ,然后在 间连边,同时将原图中的边 连为 (因为本图是无向图所以连双向边)。
所以此时删去点 就等价于将边 删去,由此可以建模网络流求最小割即最大流。
具体处理:如果点 为 或 的话,我们就将边 的容量记为 ,否则为 ,然后将边 的容量均记为 ,由此求出的最小割即为要删去的点的个数。
#include <iostream>
#include <cstring>
#include <iomanip>
#include <cmath>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(x) cout << #x << "=" << x << "\n";
int n, m, s, t;
const int maxn = 500, maxm = 3000;
const int INF = 1 << 31 - 1;
struct Edge
{
int to, cap, next;
} edge[maxm];
int head[maxn], cur[maxn], level[maxn];
int tot = 2;
void add_edge(int u, int v)
{
int u_out = u + n;
int v_in = v;
edge[tot] = {v_in, INF, head[u_out]};
head[u_out] = tot++;
edge[tot] = {u_out, 0, head[v_in]};
head[v_in] = tot++;
}
bool bfs(int s, int t)
{
queue<int> q;
q.push(s);
memset(level, -1, sizeof(level));
level[s] = 0;
while (!q.empty())
{
int u = q.front();
q.pop();
if (u == t)
return true;
for (int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].to;
if (level[v] == -1 && edge[i].cap > 0)
{
level[v] = level[u] + 1;
q.push(v);
}
}
}
return false;
}
int dfs(int u, int t, int flow)
{
if (u == t)
return flow;
int used = 0;
for (int &i = cur[u]; i; i = edge[i].next)
{
int v = edge[i].to;
if (edge[i].cap <= 0)
continue;
if (level[v] == level[u] + 1)
{
int w = dfs(v, t, min(flow - used, edge[i].cap));
if (w)
{
edge[i].cap -= w;
edge[i ^ 1].cap += w;
used += w;
if (used == flow)
break;
}
}
}
return used;
}
int solve(int s, int t)
{
int max_flow = 0;
while (bfs(s, t))
{
memcpy(cur, head, sizeof(head));
max_flow += dfs(s, t, INF - 1);
}
return max_flow;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> s >> t;
for (int x = 1; x <= n; x++)//拆点
{
int cap = ((x == s || x == t) ? INF : 1);
edge[tot] = {x + n, cap, head[x]};
head[x] = tot++;
edge[tot] = {x, 0, head[x + n]};
head[x + n] = tot++;
}
for (int i = 1; i <= m; i++)
{
int u, v;
cin >> u >> v;
add_edge(u, v);
add_edge(v, u);
}
cout << solve(s, t + n);
return 0;
}