poj 3281 Dining

15 篇文章 0 订阅
3 篇文章 0 订阅

Problem

poj.org/problem?id=3281
vjudge.net/contest/68128#problem/B

Reference

(拆点+最大流)POJ3281 Dining

Meaning

有 n 只牛、f 种食物、d 种饮料,每只牛都有喜欢的食物和饮料。现在要给尽量多的牛配一种它喜欢的食物和一种喜欢的饮料,问最多能满足多少只牛。

Analysis

最大流。把饮料和食物分隔在牛的两边,即从饮料到牛连边、从牛到食物连边。因为要保证每只牛都最多只拿到一种食物、一种饮料,即容量限制在牛的点上,就考虑把每只牛拆成两个点,中间连一条容量为 1 的边。
最终建图形如:
超级源点 -> 饮料 -> 牛(入)-> 牛(出)-> 食物 -> 超级汇点
曾经想的是另一种建图:源点连所有牛,容量为 2;每只牛向它喜欢的饮料、食物连容量为 1 的边;所有饮料、食物向汇点连容量为 1 的边。然而失败了。说不上来哪里错了,但又好像是有哪里不对。

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define Cow(x) (x + d)
#define Drink(x) (x)
#define Food(x) (x + d + 2 * n)
const int N = 100, F = N, D = N;
const int VERTEX = 2 * N + F + D + 2;
const int EDGE = D + N + F + N * (D + F);

int head[VERTEX], to[EDGE<<1], nxt[EDGE<<1], cap[EDGE<<1], sz;

void add_edge(int f, int t, int c)
{
    to[sz] = t;
    cap[sz] = c;
    nxt[sz] = head[f];
    head[f] = sz++;
}

int dis[VERTEX];
queue<int> que;

int bfs(int s, int t)
{
    memset(dis, 0, sizeof dis);
    dis[s] = 1;
    que.push(s);
    for(int tp; !que.empty(); que.pop())
    {
        tp = que.front();
        for(int i = head[tp]; ~i; i = nxt[i])
            if(!dis[to[i]] && cap[i] > 0)
            {
                dis[to[i]] = dis[tp] + 1;
                que.push(to[i]);
            }
    }
    return dis[t];
}

int cur[VERTEX];

int dfs(int now, int t, int aug)
{
    if(now == t || !aug)
        return aug;
    int flow = 0, f;
    for(int &i = cur[now]; ~i; i = nxt[i])
        if(dis[to[i]] == dis[now] + 1 &&
            (f = dfs(to[i], t, min(cap[i], aug))) > 0)
        {
            cap[i] -= f;
            cap[i^1] += f;
            flow += f;
            aug -= f;
            if(!aug)
                break;
        }
    return flow;
}

int dinic(int s, int t)
{
    int flow = 0;
    while(bfs(s, t))
    {
        for(int i = 0; i <= t; ++i)
            cur[i] = head[i];
        flow += dfs(s, t, 1);
    }
    return flow;
}

int main()
{
    int n, f, d;
    scanf("%d%d%d", &n, &f, &d);
    // 源点、汇点
    int S = 0, T = 2 * n + f + d + 1;
    memset(head, -1, sizeof head);
    sz = 0;
    // 源点到饮料
    for(int did = 1; did <= d; ++did)
    {
        add_edge(S, Drink(did), 1);
        add_edge(Drink(did), S, 0);
    }
    // 食物到汇点
    for(int fid = 1; fid <= f; ++fid)
    {
        add_edge(Food(fid), T, 1);
        add_edge(T, Food(fid), 0);
    }
    // 牛入到牛出
    for(int cid = 1; cid <= n; ++cid)
    {
        add_edge(Cow(cid), Cow(cid) + n, 1);
        add_edge(Cow(cid) + n, Cow(cid), 0);
    }
    // 饮料到牛入、牛出到食物
    for(int c = 1, nf, nd; c <= n; ++c)
    {
        scanf("%d%d", &nf, &nd);
        for(int fid; nf--; )
        {
            scanf("%d", &fid);
            add_edge(Cow(c) + n, Food(fid), 1);
            add_edge(Food(fid), Cow(c) + n, 0);
        }
        for(int did; nd--; )
        {
            scanf("%d", &did);
            add_edge(Drink(did), Cow(c), 1);
            add_edge(Cow(c), Drink(did), 0);
        }
    }
    printf("%d\n", dinic(S, T));
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值