【清华集训 2017】无限之环(费用流)

题目链接

【清华集训 2017】无限之环


题解

费用流神题。

对于每一个方格延伸出去的每一根水管,有且仅有一个其他方格延伸出的水管与之相连,这样就不会漏水。即:每根水管的容量为 1 1 ,且必须满流。

然而即使产生了最优情况,整个管网也不一定是一整个联通块,而可能被分成若干块。因此,我们要对每个格点染色,相邻的两个格点,一个连源点,一个连汇点,就可以强制使每两个相邻的方格上都产生流量了。

旋转所花的步数即是费用。将一个方格拆成5个点(上下左右中),中间点连上源 or o r 汇点,并根据水管情况向四周连容量 1 1 ,费用0的边。四周视作接触点,与对应相邻的另一个接触点连容量 1 1 ,费用0的边。接下来,对于每一种形状的格子,分类讨论。

  • 射线型:让左、下、右接触点直接连接上接触点。左,右连上去,只转一次,所以费用为 1 1 。下面连上去费用就是2
  • 直角型:旋转一次后,发现上下切换 or o r 左右切换。那么上与下、左与右之间各建一条容量为 1 1 ,费用为1的边。
  • 丁字型:与第一种情况类似,左、上、右接触点直接连接下接触点。左,右连下去,只转一次,所以费用为 1 1 。上面连下去费用就是2

至此,构图已经完成,接下来只要套上模版即可。


代码

//「Luogu4003」Infinity Loop
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
#define c(k) k + (sum << 2)
#define u(k) k + turn * sum
#define r(k) k + ((turn + 1) & 3) * sum
#define d(k) k + ((turn + 2) & 3) * sum
#define l(k) k + ((turn + 3) & 3) * sum
const int maxn = 100005;
const int maxm = 2000005;
const int inf = 1000000000;
bool vis[maxn];
int n, m, cnt, sum, src, snk, ans, dis[maxn];
int tot, ter[maxm], len[maxm], wei[maxm], nxt[maxm], lnk[maxn];
inline void add(int u, int v, int w0, int w1) {
    ter[tot] = v;
    len[tot] = w0;
    wei[tot] = w1;
    nxt[tot] = lnk[u];
    lnk[u] = tot++;
//  printf("(%d, %d): (%d, %d);\n", u, v, w0, w1);
}
void addedge(int u, int v, int w0, int w1, int type) {
    if (type) {
        swap(u, v);
    }
    add(u, v, w0, w1);
    add(v, u, 0, -w1);
}
bool spfa(int s, int t) {
    queue<int> que;
    que.push(s);
    memset(dis, 0x3f, sizeof(dis));
    dis[s] = 0, vis[s] = 1;
    int w0, w1;
    for (int u, v; !que.empty(); ) {
        u = que.front();
        que.pop();
        vis[u] = 0;
        for (int i = lnk[u]; ~i; i = nxt[i]) {
            v = ter[i], w0 = len[i], w1 = wei[i];
            if (w0 && dis[v] > dis[u] + w1) {
                dis[v] = dis[u] + w1;
                if (!vis[v]) {
                    vis[v] = 1;
                    que.push(v);
                }
            }
        }
    }
    return dis[t] < inf;
}
int find(int u, int lft) {
    if (u == snk) {
        return lft;
    }
    vis[u] = 1;
    int w0, w1, tmp, res = 0;
    for (int v, i = lnk[u]; ~i && res < lft; i = nxt[i]) {
        v = ter[i], w0 = len[i], w1 = wei[i];
        if (w0 && !vis[v] && dis[u] + w1 == dis[v]) {
            tmp = find(v, min(lft - res, w0));
            if (tmp) {
                ans += tmp * w1;
                len[i] -= tmp;
                len[i ^ 1] += tmp;
                res += tmp;
            }
        }
    }
    vis[u] = 0;
    return res;
}
int mcmf() {
    int tmp, res = 0;
    while (spfa(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);
    sum = n * m;
    src = 0;
    snk = sum * 5 + 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            int turn = 0, shape;
            int type = (i ^ j) & 1;
            int id = (i - 1) * m + j;
            scanf("%d", &shape);
            if (type) {
                addedge(src, c(id), inf, 0, 0);
            } else {
                addedge(c(id), snk, inf, 0, 0);
            }
            if (i > 1) {
                addedge(d(id - m), u(id), 1, 0, type);
            }
            if (j > 1) {
                addedge(r(id - 1), l(id), 1, 0, type);
            }
            if (shape & 1) {
                addedge(u(id), c(id), 1, 0, type);
                cnt++;
            }
            if (shape & 2) {
                addedge(r(id), c(id), 1, 0, type);
                cnt++;
            }
            if (shape & 4) {
                addedge(d(id), c(id), 1, 0, type);
                cnt++;
            }
            if (shape & 8) {
                addedge(l(id), c(id), 1, 0, type);
                cnt++;
            }
            switch (shape) {
            case 8:
                turn++;
            case 4:
                turn++;
            case 2:
                turn++;
            case 1:
                addedge(r(id), u(id), 1, 1, type);
                addedge(d(id), u(id), 1, 2, type);
                addedge(l(id), u(id), 1, 1, type);
                break;
            case 9:
                turn++;
            case 12:
                turn++;
            case 6:
                turn++;
            case 3:
                addedge(d(id), u(id), 1, 1, type);
                addedge(l(id), r(id), 1, 1, type);
                break;
            case 13:
                turn++;
            case 14:
                turn++;
            case 7:
                turn++;
            case 11:
                addedge(d(id), l(id), 1, 1, type);
                addedge(d(id), u(id), 1, 2, type);
                addedge(d(id), r(id), 1, 1, type);
                break;
            }
        }
    }
    cnt >>= 1;
    if (mcmf() == cnt) {
        printf("%d\n", ans);
    } else {
        puts("-1");
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值