【ACWing】407. 稳定的牛分配

题目地址:

https://www.acwing.com/problem/content/409/

农夫约翰的 N N N头奶牛住在 B B B个谷仓里,每个谷仓的容量有限,有的牛很喜欢现在的住所,而有的则对现在的住所非常不满意。农夫约翰打算重新安排奶牛的住所,使得它们的幸福感尽可能的接近,哪怕这会使所有牛都对安排产生不满。每头奶牛都给了约翰一个住所幸福感列表,被安排的谷仓在列表中的排名将直接影响牛的幸福感高低。你需要给定一个合理的安排,使得每个谷仓安排的牛的数量不能超过容量上限,并且幸福感最高的牛和幸福感最低的牛的幸福感差距尽量的小。换句话说,对住所最满意的牛被安排的住所在其列表中的排名和对住所最不满意的牛被安排的住所在其列表中的排名之间相差最小。

输入格式:
1 1 1行包含两个整数 N N N B B B。第 2.. N + 1 2..N+1 2..N+1行,每行包含 B B B个整数,第 i + 1 i+1 i+1行描述了第 i i i头牛的住所幸福感列表,越靠前的住所牛越满意。第 N + 2 N+2 N+2行,包含 B B B个整数,第 i i i个整数表示第 i i i间谷仓的容量。

输出格式:
输出一个整数,表示牛被安排的住所在列表上的排名的范围是多少。例如,一共 4 4 4头牛, 3 3 3头被安排在满意度排名 1 1 1的谷仓, 1 1 1头被安排在满意度排名 2 2 2的谷仓,则范围是 [ 1 , 2 ] [1,2] [1,2],输出 2 2 2

数据范围:
1 ≤ N ≤ 1000 1≤N≤1000 1N1000
1 ≤ B ≤ 20 1≤B≤20 1B20

题目的意思是要将这些牛安排进住所,使得牛的满意度的范围尽可能的小。容易看出这是个二分图匹配问题,可以用网络流来做。我们考虑对于某个事先给定的排名范围 [ l , r ] [l,r] [l,r],如何判断是否存在合法的匹配。首先将所有的牛的作为 N N N个顶点放在左部,然后将所有的住所作为 B B B个顶点放在右部,最左边放一个源点 S S S,并将其与每个牛连一条边,容量是 1 1 1(对应着每头牛只能安排进一个住所),再在最右边放一个汇点 T T T,并将每个住所向汇点连一条边,容量是这个住所的容量,对应的是住所的容量限制。对于牛与住所的连边,我们只连每头牛与其喜好排名在 [ l , r ] [l,r] [l,r]的住所之间连边,容量也是 1 1 1。这样每个整数满流就对应着一种喜好排名都位于 [ l , r ] [l,r] [l,r]的安排方案,而每个喜好排名都位于 [ l , r ] [l,r] [l,r]的安排方案也对应着一个整数满流。由于Dinic在本算法里处理的都是整数边,所以Dinic求得的最大流就是整数流,所以跑一遍Dinic之后,如果最大流刚好等于牛的数量,则存在方案,否则不存在。

具体要求最小排名范围,可以用同向双指针来做,枚举最大排名 r r r,然后求出最大的小于等于 r r r l l l使得 [ l , r ] [l,r] [l,r]对应的方案存在。容易看出 r r r右移的时候 l l l不用回退(回退只能得到更差的解)。所以该双指针算法能得到线性复杂度。整个复杂度就是 B B B乘以建图加Dinic算法的复杂度。代码如下:

#include <iostream>
#include <cstring>
using namespace std;

const int N = 1050, M = (20 * N + N + 20) * 2, INF = 1e8;
int n, m, S, T;
int a[N][25], c[N];
int h[N], e[M], ne[M], f[M], idx;
int q[N], d[N], cur[N];

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

bool bfs() {
    int hh = 0, tt = 0;
    memset(d, -1, sizeof d);
    q[tt++] = S, d[S] = 0, cur[S] = h[S];
    while (hh < tt) {
        int t = q[hh++];
        for (int i = h[t]; ~i; i = ne[i]) {
            int v = e[i];
            if (d[v] == -1 && f[i]) {
                d[v] = d[t] + 1;
                if (v == T) return true;

                cur[v] = h[v];
                q[tt++] = v;
            }
        }
    }
    
    return false;
}

int dfs(int u, int limit) {
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {
        int v = e[i];
        cur[u] = i;
        if (d[v] == d[u] + 1 && f[i]) {
            int t = dfs(v, min(limit - flow, f[i]));
            if (!t) d[v] = -1;
            f[i] -= t, f[i ^ 1] += t, flow += t;
        }
    }

    return flow;
}

int dinic() {
    int r = 0, flow;
    while (bfs()) while (flow = dfs(S, INF)) r += flow;
    return r;
}

bool check(int l, int r) {
    memset(h, -1, sizeof h), idx = 0;

    for (int i = 1; i <= n; i++) add(S, i, 1);
    for (int i = n + 1; i <= m + n + 1; i++) add(i, T, c[i - n]);
    for (int i = 1; i <= n; i++)
        for (int j = l; j <= r; j++)
        	// 只连排名范围内的边
            add(i, n + a[i][j], 1);

    return dinic() == n;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> a[i][j];

    for (int i = 1; i <= m; i++) cin >> c[i];
    S = 0, T = n + m + 1;

    int res = m;
    // 双指针
    for (int l = 0, r = 0; r <= m; r++) {
        while (l <= r && check(l, r)) {
            res = min(res, r - l + 1);
            l++;
        }
    }

    cout << res << endl;

    return 0;
}

时间复杂度 O ( ( N + B ) 2 N B ) O((N+B)^2NB) O((N+B)2NB),空间 O ( N + B ) O(N+B) O(N+B)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值