2020 icpc 昆明 B. Chessboard 有源汇有上下界最小费用可行流 强制满流

题意

给一个 n × m ( n , m ≤ 50 ) n \times m (n, m \leq 50) n×m(n,m50) 的二维网格,每个格子可以什么都不放,或者放一个黑色棋子,或者放一个白色棋子。对于每一个格子放黑色/白色棋子都会获得对应的代价。每行以及每列的黑棋个数-白棋个数都分别有各自的 [ l , r ] [l, r] [l,r] 的限制。求最小代价。

解法

比较显然是一个网络流问题,难点在于上下界和负环两个问题叠加在一起的处理。

假设先把全部格子涂成白色,再考虑反悔将每个格子变成空地或者黑色。考虑费用流建图, s s s 向每一行连有上下界的边,每一列向 t t t 连有上下界的边,第 i i i 行向第 j j j 列连一条容量为1、费用为 − s w [ i ] [ j ] -sw[i][j] sw[i][j] 的边,和一条容量为1、费用为 + s b [ i ] [ j ] +sb[i][j] +sb[i][j] 的边。计算最小费用可行流即可。注意到由于 s w [ i ] [ j ] sw[i][j] sw[i][j] s b [ i ] [ j ] sb[i][j] sb[i][j] 都是非负数,在求解最小费用可行流的时候一定是先取 − s w [ i ] [ j ] -sw[i][j] sw[i][j] 的边,符合题意。

建图如下图所示。
在这里插入图片描述
求解上下界网络流时需要强制下界满流,要从 t t t s s s 添加一条流量正无穷,费用为 0 0 0 的边。这样就会产生一个费用负圈,如图中的 s → x → y → t → s s \rightarrow x \rightarrow y \rightarrow t \rightarrow s sxyts

为了消除掉负圈的影响,可以让所有负费用的边强制满流。这样做之后我们破坏了平衡条件,但满足了最优条件。具体实现相似于上下界网络流中的将下界满流的操作。这个做法可以消除所有负边, 同时正确处理所有负圈问题。

不妨设有一条从 u u u v v v 的容量为 c c c 费用为 d d d 的边 ( d < 0 ) (d<0) (d<0)。先强制满流,把答案加上 c ⋅ d c \cdot d cd。之后,从 u u u t t t ttt ttt s s s sss sss v v v 各连一条容量为 c c c,费用为 0 0 0 的边,用来调整流量。这两条边要使用手段强制满流。最后,连一条从 v v v u u u 的容量为 c c c 费用为 − d −d d 的边,用于退流。

建图如下图所示。
在这里插入图片描述

s s s sss sss 为源点、以 t t t ttt ttt 为汇点跑一次费用流将负权边强制流满,将无穷边删掉,再以 s s ss ss 为源点 以 t t tt tt 为汇点跑一次费用流。两次费用加起来即为原图的最小费用可行流。

由于最先假定二维网格上全是白色棋子,所以答案要加上每个格子白色棋子的代价之和。后面又将代表移除白色棋子的边强制流满,便又要减去每个格子白色棋子的代价之和。二者抵消便无需额外计算。

代码如下。

#include <bits/stdc++.h>
using namespace std;

#define int int64_t
const int maxn = 120;
const int maxm = 1e7+10;
const int INF = 0x3f3f3f3f3f3f3f3f;

int h[maxn], e[maxm], f[maxm], w[maxm], ne[maxm], top;
int d[maxn], pre[maxn], incf[maxn];
bool st[maxn];

void add(int a, int b, int c, int d) {
    e[top] = b, f[top] = c, w[top] = d, ne[top] = h[a], h[a] = top++;
    e[top] = a, f[top] = 0, w[top] = -d, ne[top] = h[b], h[b] = top++;
}

bool spfa(int s, int t) {
    queue<int> q;
    memset(d, 0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q.push(s), d[s] = 0, incf[s] = INF;
    while(q.size()) {
        int u = q.front(); q.pop();
        st[u] = false;
        for(int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            if(f[i] && d[v] > d[u] + w[i]) {
                d[v] = d[u] + w[i];
                pre[v] = i;
                incf[v] = min(f[i], incf[u]);
                if(!st[v]) {
                    q.push(v);
                    st[v] = true;
                }
            }
        }
    }
    return incf[t] > 0;
}

int maxflow, mincost;
void EK(int s, int t){
    maxflow = mincost = 0;
    while(spfa(s, t)) {
        int ff = incf[t];
        maxflow += ff, mincost += ff * d[t];
        for(int i = t; i != s; i = e[pre[i]^1]) {
            f[pre[i]] -= ff;
            f[pre[i]^1] += ff;
        }
    }
}

int n, m;
int sb[55][55];
int sw[55][55];
int l[55], r[55], L[55], R[55];

int A[maxn];
int D[maxn];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    memset(h, -1, sizeof h); 
    int s   = maxn - 1, t   = maxn - 2; //原图源汇点
    int ss  = maxn - 3, tt  = maxn - 4; //用于处理上下界
    int sss = maxn - 5, ttt = maxn - 6; //用于处理负环

    cin >> n >> m;

    for(int i = 1; i<=n; i++) for(int j = 1; j<=m; j++) cin >> sb[i][j];
    for(int i = 1; i<=n; i++) for(int j = 1; j<=m; j++) cin >> sw[i][j];


    for(int i = 1; i<=n; i++) cin >> l[i] >> r[i];
    for(int i = 1; i<=m; i++) cin >> L[i] >> R[i];


    for(int i = 1; i<=n; i++) {
        int lo = l[i] + m, hi = r[i] + m;
        add(s, i, hi - lo, 0);
        A[s] -= lo; A[i] += lo;
    }

    for(int i = 1; i<=m; i++) {
        int lo = L[i] + n, hi = R[i] + n;
        add(n + i, t, hi - lo, 0);
        A[n+i] -= lo; A[t] += lo;
    }

    for(int i = 1; i<=n; i++) {
        for(int j = 1; j<=m; j++) {
            add(n+j, i, 1, sw[i][j]);
            D[i] -= 1; D[n+j] += 1;
            add(i, n+j, 1, sb[i][j]);
        }
    }

    for(int i = 0; i<maxn; i++) {
        if(A[i] > 0) add(ss, i, A[i], 0);
        else if(A[i] < 0) add(i, tt, -A[i], 0);
    }
    add(t, s, INF, 0);

    for(int i = 0; i<maxn; i++) {
        if(D[i] > 0) add(sss, i, D[i], 0);
        if(D[i] < 0) add(i, ttt, -D[i], 0);
    }
    add(tt, ss, INF, 0);

    EK(sss, ttt);
    int cost1 = mincost;
    f[top-1] = f[top-2] = 0;

    EK(ss, tt);
    int cost2 = mincost;

    cout << cost1 + cost2 << endl;
}

参考博客

费用流处理负圈的方法
最小费用流—ZKW算法
有上下界的(费用)网络流全解
2021ICPC 昆明区域赛 B.Chessboard(上下界最小费用最大流)
第 45 届ICPC亚洲区域赛(昆明)B Chessboard 上下界费用流 负环处理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FengLing255

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值