[SMOJ2206]圆桌问题

97 篇文章 0 订阅
10 篇文章 0 订阅

这题与飞行员配对方案问题有些类似,都是匹配问题,但又不尽相同。只要把最关键的部分——建模,搞定之后,整个题目迎刃而解,后面跑最大流就不提了。

还是用类似的套路,我们发现整个题目可以转化为二分图的模型,左边的点表示就餐的代表,右边的点表示一张张餐桌。左边是不是每个人一个结点呢?当然不可能,那样会导致结点数太多,而且对于“同一个单位”也没有办法处理。因此我们规定,左边的点是一个个单位。
那么,按照之前我所说的,在建模的时候一定要明确,点、边和流的含义。点,我们已经规定好了。边,则与第一题是一样的,表示某个单位的人能去某张餐桌就餐,考虑到本题并没有限制能坐哪些餐桌,因此左边每个点都要向右边每个点连一条容量为 1 的边,注意容量为 1 的用意,这恰好限定了每个单位不会有多于 1 人在同一张餐桌就餐。而从源点向左边每个点所连边的容量,就是各个单位的代表数。从右边每个点向汇点所连边的容量,就是各张餐桌能容纳的代表数。

例如,对于样例:

最后明确“流”的含义,一条流量为 1 的 suvt 的路径表示的含义是 “一名 u 单位的代表到 v 餐桌就餐了”。最后回顾一下本题,我们通过对容量和连边的设定,满足了题目中的所有限制:

  • 单位代表数限制。从源点向左边代表各单位的点连边的容量恰好为该单位的代表数,不会导致多流。
  • 餐桌人数限制。从右边代表各餐桌的点向汇点连边的容量恰好为该餐桌能容纳的最大人数,对于一个合法的流,流量不可能超过容量,因此满足限制。
  • 同一单位的人不能在同一餐桌就餐。左边点与右边点之间的连边,容量都为 1,保证同一个单位不会有多于 1 人出现在同一张餐桌。

总结归纳一下,可以发现,“容量限制”的性质对网络流建图是很有帮助的。在二分图的匹配问题中,更是如此。

还有一个问题就是方案的输出,其实也不难。在最后一次增广结束后,检查残余网络中左边每个点向右边的连边,若残量为 0,则说明从这条边上流了一个单位的流量,即左边这个单位有一个人到右边这张餐桌就餐了,相应输出即可。

参考代码:

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

using namespace std;

const int MAXM = 200;
const int MAXN = 300;
const int INF = 0x3f3f3f3f;

struct Edge {
    Edge *next;
    int cap;
    int dest;
} edges[MAXM * MAXN << 1], *current, *first_edge[MAXM + MAXN];

int m, n, s, t;
bool vis[MAXM + MAXN];

Edge *counterpart(Edge *x) {
    return edges + ((x - edges) ^ 1);
}

void insert(int u, int v, int c) {
    current -> next = first_edge[u];
    current -> cap = c;
    current -> dest = v;
    first_edge[u] = current ++;
}

int dfs(int u, int f) {
    if (u == t) return f;
    if (vis[u]) return 0; else vis[u] = true;
    for (Edge *p = first_edge[u]; p; p = p -> next) 
        if (p -> cap)
            if (int res = dfs(p -> dest, min(f, p -> cap))) {
                p -> cap -= res;
                counterpart(p) -> cap += res;
                return res;
            }
    return 0;
}

int main(void) {
    freopen("2206.in", "r", stdin);
    freopen("2206.out", "w", stdout);
    scanf("%d%d", &m, &n); current = edges;
    s = 0; t = m + n + 1;
    fill(first_edge, first_edge + t + 1, (Edge*)0);
    for (int i = 1; i <= m; i++)
        for (int j = m + 1; j < t; j++) insert(i, j, 1), insert(j, i, 0);

    int total = 0;
    for (int i = 1; i <= m; i++) {
        int r; scanf("%d", &r); total += r;
        insert(0, i, r); insert(i, 0, 0);
    }
    for (int i = m + 1; i < t; i++) {
        int c; scanf("%d", &c);
        insert(i, t, c); insert(t, i, 0);
    }

    int ans = 0;
    while (true) {
        memset(vis, false, sizeof vis);
        if (int res = dfs(s, INF)) ans += res; else break;
    }
    printf("%d\n", ans == total);
    if (ans == total) {
        for (int i = 1; i <= m; i++) {
            for (Edge *p = first_edge[i]; p; p = p -> next)
                if (!p -> cap) printf("%d ", p -> dest - m);
            putchar('\n');
        }
    }
    return 0;
}


ps. 这题的 lemon_judge 是我写的,一开始出了点偏差,很惭愧。发现问题后及时作了修正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值