打鸟问题 | 网络流:EK算法

打鸟问题

成绩10开启时间2020年04月21日 星期二 17:15
折扣0.8折扣时间2020年05月30日 星期六 23:55
允许迟交关闭时间2020年05月30日 星期六 23:55

在一个 n * m (0 < m, n <=100) 的方阵中有 k(0<k<=n*m) 只鸟,一个人持枪可以在每行开一枪打死该行中所有鸟或者在每列开一枪打死该列中所有鸟,由于每一行和列地理位置等因素影响,在第 i 行开一枪会有一个花费 ri ,在第 j 列开一枪会有一个花费 cj , (0<=ri, cj<=10000) ,现已知 n, m, k 以及每一行和每一列上开一枪的花费,请问打死所有鸟所需的最小总花费。

输入 ]

输入包含多组测试数据。

每组测试数据第一行有三个整数 n, m, k 。

接下来一行有 n 个整数,第 i 个整数表示在第 i 行开一枪所需的花费 ri 。

接下来一行有 m 个整数,第 j 个整数表示在第 j 行开一枪所需的花费 cj 。

接下来 k 行,每行有两个整数 x, y (0<x<=n, 0<y<=m) 表示在第 x 行 y 列上有一只鸟。我们保证一个格子内至多有一只鸟。

输出 ]

对于每组测试数据,请输出一行,包含一个整数,即打死该图中所有鸟所需的最小花费值。

 测试输入期待的输出时间限制内存限制额外进程
测试用例 1以文本方式显示
  1. 2 2 1↵
  2. 1 2↵
  3. 2 1↵
  4. 1 1↵
  5. 2 3 2↵
  6. 100 2↵
  7. 1 1 1↵
  8. 1 1↵
  9. 1 2↵
以文本方式显示
  1. 1↵
  2. 2↵
1秒64M0

 


一、建模

本题可以转化为一个网络流问题,按照如下建图

🔺点的设置:

  • 行对应 n 个点
  • 列对应 m 个点
  • 分别设置一个源点s、汇点e

🔺边的设置:

  • s 到 n 个行节点的边权设置为时间消费
  • n 个列节点到 e 的边权设置为时间消费
  • 每一个鸟的坐标(x,y)设置对应行节点到列节点边权为无穷大
  • 其他边设置为 0

      建图(注意点的标号哈!需要小改动)完成后直接用EK算法的板子求图的最大流,即为打死所有鸟所需的最小总花费。


二、EK 算法剖析     

     上板子上板子!直接看这个:网络流初步最大流(EK算法和Dinic算法进阶),代码没有问题!

     关于 EK 算法的理解留一个坑....以后补上


三、 AC 代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int MAXN = 605;
const int INF = 0x7fffffff;

int n, m, k;
int s, e;   //源点与汇点
int map[MAXN][MAXN];  //记录当前图的残余部分

bool Init() {
    memset(map, 0, sizeof(map));  //一定要初始化零边!
    if (EOF == scanf("%d %d %d", &n, &m, &k))
        return false;   //输入终止
    s = 0;   //初始化远点
    e = n + m + 1;  //初始化汇点
    
    for (int i = 1; i <= n; i++)
        scanf("%d", &map[s][i]);
    for (int i = n + 1; i <= n + m; i++)
        scanf("%d", &map[i][e]);

    int p, q;
    for (int i = 1; i <= k; i++) {
        scanf("%d %d", &p, &q);
        map[p][q + n] = INF;   //有鸟的点,xy坐标边权设置为无穷大
    }
    return true;
}

bool vis[MAXN];
int pre[MAXN];   //储存增广路径,pre[x] = y:x点的前驱节点是y

/* 找到一条从 start 到 end 的增广路径 */
bool Bfs() {
    queue<int> Q;
    memset(pre, -1, sizeof(pre));
    memset(vis, false, sizeof(vis));
    pre[s] = s;
    vis[s] = true;
    Q.push(s);  //最初将源点加入队列

    while (!Q.empty()) {
        int v = Q.front();   //取出一个节点
        Q.pop();
        for (int next = 1; next <= e; next++) {
            if (map[v][next] > 0 && !vis[next]) {
                vis[next] = true;
                pre[next] = v;
                if (next == e)   //找到终点
                    return true;
                Q.push(next);
            }
        }
    }
    return false;
}

/* 寻找增广路径上的最小权值 */
int CalcIncrease() {
    int ans = INF;
    for (int i = e; i != s; i = pre[i])
        ans = min(map[pre[i]][i], ans);
    return ans;
}

int EdmondsKarp() {
    int max_flow = 0;
    /* 当可以找到一条增广路径时 */
    while (Bfs()) {
        int increase = CalcIncrease();   //计算选择该增广路径的增量
        /* 依次讨论增广路径上的每一条边 */
        for (int i = e; i != s; i = pre[i]) {
            /* 计算残余图的权值 */
            map[pre[i]][i] -= increase;  
            map[i][pre[i]] += increase;
        }
        max_flow += increase;  //更新最大流
    }
    return max_flow;
}

int main() {
    while (Init())
        printf("%d\n", EdmondsKarp());
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值