Hihocoder 1424 Asa's Chess Problem (有源汇上下界最小费用流)

Problem

Asa comes up with a chess problem. There are N×N chesses on a board with N×N grids, one chess in one grid. Some chesses are black while others are white.

The N×N grids are divided into (N×N) / 2 pairs(N is even), and each grid only belongs to one pair. The two grids of a pair are in the same row or the same column. We can swap the chesses in a pair of grids. Suppose the number of black chesses in row i is R[i], and the number of black chesses in column j is C[j]. The problem is whether there is a solution satisfy that Rl[i] <= R[i] <= Rh[i] and Cl[j] <= C[j] <= Ch[j]. Rl[i], Rh[i], Cl[j] and Ch[j] are constant integers.

Please calculate the minimum number of swaps Asa needed to make the chess board satisfy the restriction.

限制条件

最多 100 组样例

2N50

解题思路

有源汇的上下界最小费用流。

首先构造有上下界的图,定义源点 S 和 汇点 T 。

  • 从 S 向每一行(编号 1~N)、每一列(编号 N+1~N+N)连一条有上下界的边,流量下界=流量上界=该行(列)的黑子个数。
  • 从每一行向 T 连一条有上下界的边,流量下界为 Rl[i] ,上界为 Rh[i]
  • 每一列类似
  • 对于每一对 Pair,判断其是否同色,同色则对结果无影响,不考虑;不同色则判断其同行 OR 同列,同行则表示其交换对对应两列产生影响(对行无影响),从当前 pair 中代表黑子的列向代表白子的列连一条边,流量上下界分别为 0,1 。单位费用为 1 。同列类似。

此时问题可以看作求有源汇的上下界费用流求 ST 流量为总黑子个数的最小费用。

求解有源汇上下界费用流可转换为求无源汇上下界最小费用可行循环流,通过 TS 连边,流量上下界为 总黑子个数, INF 。

求解无源汇上下界最小费用可行循环流,重新构图:

  1. 在原基础上再新增一个超级源点 supS,supT,构造只有上界的网络。
  2. 对于原图的每一条边 (u, v) ,再新图中添加一条 supSv 流量为 u,v 流量下界的边。一条 usupT 流量为 u,v 流量下界的边,一条 uv 流量为 u,v - 的边。

做从 supSsupT 的最小费用流,限定到达 supT 的流量为满流(即 supS 所有出边的流量和)。此即为答案。

HINT: 原图中所有未提及的边费用都应记为 0 。新图中的重新构造的边的费用等同原图中对应边的费用。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 50 + 10;
int n, chs[N][N], rl[N], rh[N], cl[N], ch[N], r[N], c[N], s, t, sups, supt, digflow;
const int maxv = 100 + 10;  //最大顶点数
const int inf = 2e9;    //应大于总费用和
typedef pair<int, int> P;   //first保存最短距离,second保存顶点编号
struct Edge{    int to,cap,rev, cost;}e;
vector<Edge> g[maxv];   //图的邻接表表示
int h[maxv],dist[maxv]; //顶点的势、最短距离,若cost为整数,改INT
int prevv[maxv],preve[maxv],V;//最短路中的前驱节点、对应的边、顶点数
void addedge(int from, int to, int cap, int cost) { //加边
    e.to = to,  e.cap = cap,    e.rev = g[to].size(),   e.cost = cost;
    g[from].push_back(e);
    e.to = from,    e.cap = 0,  e.rev = g[from].size() - 1, e.cost = -cost;
    g[to].push_back(e);
}
void addedge(int from, int to, int low, int up, int cost) {
    digflow += low;
    addedge(sups, to, low, cost);
    addedge(from, supt, low, cost);
    addedge(from, to, up-low, cost);
}
int mincostflow(int s,int t,int f) {    //求解从s到t,流量为f的最小费用流
    int res = 0;
    memset(h,0,sizeof(4*t+4));
    while(f>0) {
        priority_queue<P, vector<P>, greater<P> > que;
        fill(dist,dist+V,inf);
        dist[s] = 0;
        que.push(P(0,s));
        while(!que.empty()) {
            P p = que.top();    que.pop();
            int v = p.second;
            if(dist[v] < p.first)   continue;
            for(int i=0;i<g[v].size();i++) {
                Edge &E = g[v][i];
                if(E.cap > 0 && dist[E.to] > dist[v] + E.cost + h[v] - h[E.to]) {
                    dist[E.to] = dist[v] + E.cost + h[v] - h[E.to];
                    prevv[E.to] = v;
                    preve[E.to] = i;
                    que.push(P(dist[E.to],E.to));
                }
            }
        }
        if(dist[t] == inf)  return -1;
        for(int v = 0;v < V;v++)    h[v] += dist[v];
        //沿s到t的最短路尽量增广
        int d = f;
        for(int v = t;v!=s;v=prevv[v])
            d = min(d,g[prevv[v]][preve[v]].cap);
        f -= d;
        res += h[t] * d;
        for(int v = t;v!=s;v=prevv[v]) {
            Edge &E = g[prevv[v]][preve[v]];
            E.cap -= d;
            g[v][E.rev].cap += d;
        }
    }
    return res;
}
int main()
{
    while(scanf("%d", &n)!=EOF)
    {
        digflow = 0;
        s = 2*n + 1;    sups = 0;
        t = 2*n + 2;    supt = 2*n+3;
        memset(c, 0, sizeof(c));
        memset(h, 0, sizeof(h));
        for(int i=0;i<=supt;i++)
            g[i].clear();
        int mx = 0;
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            scanf("%d", &chs[i][j]);
            c[j] += chs[i][j];
            h[i] += chs[i][j];
            mx += chs[i][j];
        }   
        for(int i=1;i<=n;i++)
        {
            addedge(s, i, h[i], h[i], 0);
            addedge(s, i+n, c[i], c[i], 0);
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%d %d", &rl[i], &rh[i]);
            addedge(i, t, rl[i], rh[i], 0);
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%d %d", &cl[i], &ch[i]);
            addedge(i+n, t, cl[i], ch[i], 0);
        }
        for(int i=1, x1, y1, x2, y2;i<=n*n/2;i++)
        {
            scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
            if(chs[x1][y1] == chs[x2][y2])  continue;
            if(chs[x1][y1] == 0)    swap(x1, x2),   swap(y1, y2);
            if(x1 == x2) {
                addedge(n+y1, n+y2, 1, 1);  
            } else {
                addedge(x1, x2, 1, 1);
            }
        }

        addedge(t, s, mx, inf, 0);
        V = 2*n+4;
        int ans = mincostflow(sups, supt, digflow);
        printf("%d\n", ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值