• 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 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122

    参考博客

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

  • 相关阅读:
    工业相机飞拍模式介绍及相机曝光值计算
    Vatti clipping 算法介绍
    Etcd-v3.4.27集群部署
    ArkTS初始用体验
    Amazon MSK 基于 S3 的数据导出、导入、备份、还原、迁移方案
    商圣范蠡见好就收,散尽钱财求得好死
    新安装win11,搜索框无法输入的问题
    几个非常好用的CMD命令
    Redis 面试常见问答
    mysql(七)------数据库的索引
  • 原文地址:https://blog.csdn.net/qq_33512762/article/details/127632435