【ACWing】2179. 圆桌问题

题目地址:

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

假设有来自 m m m个不同单位的代表参加一次国际会议。每个单位的代表数分别为 r i ( i = 1 , 2 , … , m ) r_i(i=1,2,…,m) ri(i=1,2,,m)。会议餐厅共有 n n n张餐桌,每张餐桌可容纳 c i ( i = 1 , 2 , … , n ) c_i(i=1,2,…,n) ci(i=1,2,,n)个代表就餐。为了使代表们充分交流,希望从同一个单位来的代表不在同一个餐桌就餐。试设计一个算法,给出满足要求的代表就餐方案。

输入格式:
1 1 1行有 2 2 2个正整数 m m m n n n m m m表示单位数, n n n表示餐桌数。第 2 2 2行有 m m m个正整数,分别表示每个单位的代表数 r i r_i ri。第 3 3 3行有 n n n个正整数,分别表示每个餐桌的容量 c i c_i ci

输出格式:
如果问题有解,在第 1 1 1行输出 1 1 1,否则输出 0 0 0。接下来的 m m m行给出每个单位代表的就餐桌号。如果有多个满足要求的方案,只要求输出 1 1 1个方案。

数据范围:
1 ≤ m ≤ 150 1≤m≤150 1m150
1 ≤ n ≤ 270 1≤n≤270 1n270
1 ≤ r i , c i ≤ 100 1≤r_i,c_i≤100 1ri,ci100

二分图多重匹配,可以用最大流来做。建图方面,可以将 m m m个不同单位看成左部的 m m m个点, n n n个桌子可以看成右部的 n n n个点,最左边加一个源点,并且从源点向左部每个点连一条边,容量就是左部点的那个单位的代表数;再在最右边加一个汇点,并且从右部每个点向汇点连一条边,容量就是右部点的桌子的可容纳的代表数;左部点向右部点连边连满,每条边的容量都是 1 1 1(对应的是每个单位只能派一个代表去某个特定的桌子)。那么,对于任意一种安排的方案,如果某个单位派了人去某个桌子,那么这条边的流量就是 1 1 1,否则就是 0 0 0,所以可以对应一个网络里的可行流;对于任一个整数可行流,看中间的边,谁满了,就说明是哪个单位向哪个桌子派了一个代表。存在可行方案,意味着存在一个流量为 ∑ r i \sum r_i ri的可行流。具体求最大流可以用Dinic算法来做。代码如下:

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

const int N = 430, M = (150 * 270 + N) * 2, INF = 1e8;
int n, m, S, T;
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]) {
        cur[u] = i;
        int v = e[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;
}

int main() {
    scanf("%d%d", &m, &n);
    S = 0, T = m + n + 1;
    memset(h, -1, sizeof h);

    int tot = 0;
    for (int i = 1; i <= m; i++) {
        int c;
        scanf("%d", &c);
        add(S, i, c);
        tot += c;
    }

    for (int i = 1; i <= n; i++) {
        int c;
        scanf("%d", &c);
        add(m + i, T, c);
    }

    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
            add(i, m + j, 1);

    if (dinic() != tot) printf("0\n");
    else {
        printf("1\n");
        // 找一下残留网络里容量为0的边
        for (int i = 1; i <= m; i++) {
            for (int j = h[i]; ~j; j = ne[j]) 
                if (e[j] > m && e[j] <= m + n && !f[j]) 
                    printf("%d ", e[j] - m);
            printf("\n");
        }
    }

    return 0;
}

时间复杂度 O ( m n ) O(mn) O(mn),空间 O ( n ) O(n) O(n)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值