【ZJOI 2009】狼和羊的故事(最小割)

本文介绍了ZJOI2009竞赛中一道名为“狼和羊的故事”的题目解决方法。该题通过建立最大流模型求解最优方案,特别讨论了非狼非羊领地的处理方式,即与周围所有节点相连,实现自由切割。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

【ZJOI 2009】狼和羊的故事


题解

如果只有 1 1 2的话,那么就将源点连羊,狼连汇点(容量 inf inf ),狼羊之间连一条容量为 1 1 的边。

但是,有的领地既不是羊的,也不是狼的。
你可能会想把0归为 1 1 (或2),但是仔细想想可以发现还有更优的解。

最终的解决方案: 0 0 点与四周的所有节点连一条边,这样就可以在0的四周自由切割,没有附加限制。


代码

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int maxl = 105;
const int maxn = 10005;
const int maxm = 60005;
const int inf = 1000000000;
int n, m, src, snk, c[maxl][maxl], dis[maxn];
int tot, ter[maxm], len[maxm], nxt[maxm], lnk[maxn];
int id(int i, int j) {
    return (i - 1) * m + j;
}
void add(int u, int v, int w) {
    ter[tot] = v;
    len[tot] = w;
    nxt[tot] = lnk[u];
    lnk[u] = tot++;
}
void adde(int u, int v, int w) {
    add(u, v, w);
    add(v, u, w);
}
int min(int x, int y) {
    return x < y ? x : y;
}
bool bfs(int s, int t) {
    queue<int> que;
    que.push(s);
    memset(dis, -1, sizeof(dis));
    dis[s] = 0;
    for (int u, v, w; !que.empty(); ) {
        u = que.front();
        que.pop();
        for (int i = lnk[u]; ~i; i = nxt[i]) {
            v = ter[i], w = len[i];
            if (w && dis[v] == -1) {
                dis[v] = dis[u] + 1;
                que.push(v);
            }
        }
    }
    return ~dis[t];
}
int find(int u, int lft) {
    if (u == snk) {
        return lft;
    }
    int tmp, res = 0;
    for (int v, w, i = lnk[u]; ~i && res < lft; i = nxt[i]) {
        v = ter[i], w = len[i];
        if (w && dis[u] + 1 == dis[v]) {
            tmp = find(v, min(w, lft - res));
            if (tmp) {
                len[i] -= tmp;
                len[i ^ 1] += tmp;
                res += tmp;
            }
        }
    }
    if (res < lft) {
        dis[u] = -1;
    }
    return res;
}
int dinic() {
    int tmp, res = 0;
    while (bfs(src, snk)) {
        tmp = find(src, inf);
        while (tmp) {
            res += tmp;
            tmp = find(src, inf);
        }
    }
    return res;
}
int main() {
    memset(lnk, -1, sizeof(lnk));
    scanf("%d %d", &n, &m);
    src = 0, snk = n * m + 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            scanf("%d", c[i] + j);
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (c[i][j] == 1) {
                adde(src, id(i, j), inf);
            } else if (c[i][j] == 2) {
                adde(id(i, j), snk, inf);
            }
        }
    }
    for (int i = 1; i < n; i++) {
        for (int j = 1; j <= m; j++) {
            if (c[i][j] == c[i + 1][j] && c[i][j]) {
                continue;
            }
            adde(id(i, j), id(i + 1, j), 1);
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < m; j++) {
            if (c[i][j] == c[i][j + 1] && c[i][j]) {
                continue;
            }
            adde(id(i, j), id(i, j + 1), 1);
        }
    }
    printf("%d\n", dinic());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值