洛谷P1345 [USACO5.4] 奶牛的电信 Telecowmunication

39 阅读4分钟

原题:P1345 [USACO5.4] 奶牛的电信 Telecowmunication

题面:

P1345 [USACO5.4] 奶牛的电信 Telecowmunication

题目描述

农夫约翰的奶牛们喜欢通过电邮保持联系,于是她们建立了一个奶牛电脑网络,以便互相交流。这些机器用如下的方式发送电邮:如果存在一个由 cc 台电脑组成的序列 a1,a2,,aca_1,a_2,\cdots ,a_c,且 a1a_1a2a_2 相连,a2a_2a3a_3 相连,等等。那么电脑 a1a_1aca_c 就可以互发电邮。

很不幸,有时候奶牛会不小心踩到电脑上,农夫约翰的车也可能碾过电脑,这台倒霉的电脑就会坏掉。这意味着这台电脑不能再发送电邮了,于是与这台电脑相关的连接也就不可用了。

有两头奶牛就想:如果我们两个不能互发电邮,至少需要坏掉多少台电脑呢?请注意,c1,c2c_1,c_2 不能被破坏。请编写一个程序为她们计算这个最小值。

以如下网络为例:

   1*
  /
 3 - 2*

这张图画的是有 22 条连接的 33 台电脑。我们想要在电脑 1122 之间传送信息。电脑 11332233 直接连通。如果电脑 33 坏了,电脑 1122 便不能互发信息了。

输入格式

第一行:四个由空格分隔的整数:N,M,c1,c2N,M,c_1,c_2NN 是电脑总数,电脑由 11NN 编号。MM 是电脑之间连接的总数。后面的两个整数 c1c_1c2c_2 是上述两头奶牛使用的电脑编号。连接没有重复且均为双向的(即如果 c1c_1c2c_2 相连,那么 c2c_2c1c_1 也相连)。两台电脑之间至多有一条连接。电脑 c1c_1c2c_2 不会直接相连。

22M+1M+1 行:接下来的 MM 行中,每行包含两台直接相连的电脑的编号。

输出格式

一行,一个整数,表示使电脑 c1c_1c2c_2 不能互相通信需要坏掉的电脑数目的最小值。

输入输出样例 #1

输入 #1

3 2 1 2
1 3
2 3

输出 #1

1

说明/提示

对于 100%100\% 的数据:1N1001\le N \le 1001M6001\le M \le 600

SolutionSolution

刚学完最大流,所以找几道练习题来做。

分析一下题目,要求我们删除最少的点,以切断 c1,c2c_1,c_2 间的连通性。来想一下做法。

首先看这个问题的性质,是不是有点像割点?用 tarjantarjan 来求割点可以吗?实际上是不行的,因为这道题中的图可能并不存在割点,需要删去多个点以切断 c1,c2c_1,c_2 间的连通性,就算存在,也不能保证 c1,c2c_1,c_2 处于不同的由这个割点分割出的子图。所以求割点在这题是没什么效果的。

那么回顾我们刚刚学过的最大流,由最大流最小割定理可得,最大流等于最小割,而最小割是什么?就是将整个图分成两个集合 S,TS,T 分别包含源点 ss 和汇点 tt ,取边集 EE 代表连接这两个点集的边的集合,让边集的边容量和最小即为最小割。

所以我们可以考虑使用求最小割的方法来求解这道题。但问题在于,这道题要求我们删点,而最小割求的是边容量的最小和,如何将删点转化为删边呢?此时就要用到一个经典的处理方式,我们将每个点一分为二,分别记为进入的点和出去的点,即 uuin,uoutu \rightarrow u_{in},u_{out} ,然后在 uin,uoutu_{in},u_{out} 间连边,同时将原图中的边 (u,v)(u,v) 连为 (uout,vin),(vout,uin)(u_{out},v_{in}),(v_{out},u_{in}) (因为本图是无向图所以连双向边)。

所以此时删去点 uu 就等价于将边 (uin,uout)(u_{in},u_{out}) 删去,由此可以建模网络流求最小割即最大流。

具体处理:如果点 uuc1c_1c2c_2 的话,我们就将边 (uin,uout)(u_{in},u_{out}) 的容量记为 INFINF ,否则为 11 ,然后将边 (uout,vin),(vout,uin)(u_{out},v_{in}),(v_{out},u_{in}) 的容量均记为 INFINF ,由此求出的最小割即为要删去的点的个数。

CodingCoding

#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;
}