poj3281 Dining(经典最大流+拆点)


http://poj.org/problem?id=3281

题意:n头牛f种食物d种饮料,每头牛都有各自喜欢的食物和饮料(不止一种),而每种食物或饮料只能分配给一头牛(数量为1)。问最多能有多少头牛可以同时得到喜欢的食物和饮料?


思路:普通建图都是超级源点与供应相连接,超级汇点与需求相连接。然而这里有两种供应一种消耗,但是只有供应和消耗之间有联系,同种消耗之间没有联系,要想建立一个连贯的图,必须将供应放在中间。于是这样就变成了源点-食物-牛-饮料-汇点这样的层次图。容量设定为1,保证每个饮料和食物只能分配1单位给牛。

接下来才是关键,早就听说必须要拆点,但一直不知道为毛要拆。看别人博客,“源点-food-牛-drink-汇点,这样虽然满足每份food和drink只能给一头牛吃,但是没法解决每头牛只能吃一份的问题。”,也就是说不拆点每头牛有可能吃两份以上。这我就想不通了,最大流算法中不是每次只能找到一条增广路吗,而且每找到一条就加上相应的流量,更何况这里都是容量为1的边,下一条不应该再路过这条边啊,既然不路过,那怎么会吃两个以上食物?模拟一下吧:


红色为已经找到的增广路,找下一条蓝色的增广路时虽然没有路过边,但依然可以经过那个点!注意我图中是按照拆点前的路线走的。问题就出在牛这里!但是为什么会这样?根本原因就是牛想要的食物不止一个,饮料也不止一个,换句话说就是两种供应引起的对道路限制的忽略。以前我做的都是一种供应,所以没有遇到这种问题,这次算是开了眼界了。这样拆点后相当于给牛这个点加了个限制,不让下一条增广路路过这里。

我觉得我说的够详细了吧。



#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <queue>
#include <stack>
#include <ctype.h>

using namespace std;

typedef long long LL;

const int N = 1000005;
const int INF = 0x3f3f3f3f;

int head[N], dis[N], cur[N];
bool vis[N];
int n, cnt, f, d;

struct Edge
{
    int to, cap, flow, next;
}edge[N];

void add(int u, int v, int w)
{
    edge[cnt] = (struct Edge){v, w, 0, head[u]};
    head[u] = cnt++;
    edge[cnt] = (struct Edge){u, 0, 0, head[v]};
    head[v] = cnt++;
}

bool bfs(int start, int endd)
{
    memset(dis, -1, sizeof(dis));
    memset(vis, false, sizeof(vis));
    queue<int>que;
    dis[start] = 0;
    vis[start] = true;
    que.push(start);
    while(!que.empty())
    {
        int u = que.front();
        que.pop();
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            Edge E = edge[i];
            if(!vis[E.to] && E.flow<E.cap)
            {
                dis[E.to] = dis[u]+1;
                vis[E.to] = true;
                if(E.to == endd) return true;
                que.push(E.to);
            }
        }
    }
    return false;
}


int dfs(int x, int res, int endd)
{
	if(x == endd || res == 0) return res;
	int flow = 0, f;
	for(int& i = cur[x]; i != -1; i = edge[i].next)
	{
		Edge E = edge[i];
		if(dis[E.to] == dis[x]+1)
		{
		    f = dfs(E.to, min(res, E.cap-E.flow), endd);
            if(f>0)
            {
                edge[i].flow += f;
                edge[i^1].flow -= f;
                flow += f;
                res -= f;
                if(res == 0) break;
            }
		}
	}
	return flow;
}


int max_flow(int start, int endd)
{
    int flow = 0;
    while(bfs(start, endd))
    {
        memcpy(cur, head, sizeof(head));
        flow += dfs(start, INF, endd);
    }
    return flow;
}

void init()
{
    cnt = 0;
    memset(head, -1, sizeof(head));
}

void getmap()
{
    int s, t, fnum, dnum;
    for(int i = 1; i <= n; i++)
    {
        scanf("%d%d", &fnum, &dnum);
        while(fnum--)//食物
        {
            scanf("%d", &s);
            add(2*n+s, i, 1);
        }
        while(dnum--)//饮料
        {
            scanf("%d", &t);
            add(n+i, 2*n+f+t, 1);
        }
        add(i, n+i, 1);
    }
    for(int i = 1; i <= f; i++)
        add(0, 2*n+i, 1);
    for(int i = 1; i <= d; i++)
        add(2*n+f+i, 2*n+f+d+1, 1);
}

int main()
{
  //  freopen("in.txt", "r", stdin);
    while(~scanf("%d%d%d", &n, &f, &d))
    {
        init();
        getmap();
        int ans = max_flow(0, 2*n+f+d+1);
        printf("%d\n", ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值