POJ-3281Dining(最大流)

最大流比较经典的题目,运用了一定的技巧

题意:

农场主有几头牛,每头牛都有想吃的和不想吃的,某天农场主忘记按照牛的喜好做饭,但是希望能尽可能的让更多的牛满足

每头牛都有可以喝的,和可以吃的,求可以的满足的牛的数量


第一次猛一看,二分匹配!!但是仔细想想,二分匹配好像解决不了这个问题。

因为二分匹配都是两两匹配,但是这道题中,每头牛要匹配喝的喝吃的,三者之间的匹配,如果强行二分匹配,两两匹配的话

无法保证尽量的让牛满足。例如:牛1吃food1 没有匹配到喝的。牛2没有吃的但是喝 drink1 。这样两头牛都没有满足。但是如果让牛1不吃,让牛2吃,那么牛2就满足了,这种情况是二分匹配满足不了的

这道题重点就是如何变成最大流问题

变成最大流主要有几个问题

1.如何建图?

2.如果简单的建图,那么会出现一头牛吃多种食物的情况,如何解决?

主要思路:

1.建图很好建图

自己创一个源点和汇点,然后让源点指向食物,食物指向牛,牛指向饮料,每条边的流量为1

这样一会在广搜的时候的邻接矩阵就建好了(当然用邻接表更OJBK,这里为了更简单一点,用邻接矩阵)

这样图就建好了

2.上面说的方法建图有很大的问题!!!

例如:F1->N1->D1 这是一条线,一种匹配方法

那么第二次匹配的时候  F2->N1->D2  这又是一种匹配方法

第一次匹配的时候,F1->N1的流量用完了,但是F2->N1的流量还有,第二次匹配也可以成功

这就是传说中的一牛吃两草(多种食物都给同一头牛吃了)

这导致只有一头牛,但是输出答案是2。

这时,就有新操作了!!

让牛指向牛!!(这种操作我也是从别人那里学的)牛一分为二,左牛和右牛

匹配的路线变为F1->N1->N1->D1

第二次匹配的路线变为F2->N1->N1->D2

但是!!第二次匹配的路线中  N1->N1这条路线流量用完了,所以第二次匹配的路线不可能匹配成功

这样就防止了多种食物匹配一头牛

#include<queue>
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=100+10;
const int inf=0x3f3f3f3f;
int e[maxn*5][maxn*5];
int n,f,d;
int EK(int s,int t)
{
    int f=0;
    queue<int>q;
    int p[maxn*5],vis[maxn*5],a[maxn*5];
    while(1)
    {
        memset(a,0,sizeof(a));
        a[s]=1;
        q.push(s);
        while(!q.empty())
        {
            int u=q.front();
            q.pop();
            for(int i=0; i<=t; i++)
            {
                if(!a[i]&&e[u][i])
                {
                    p[i]=u;
                    q.push(i);
                    a[i]=min(a[u],e[u][i]);
                    if(i==t)break;
                }
            }
        }
        if(a[t]==0)
            break;
        for(int i=t; i!=s; i=p[i])
        {
            e[p[i]][i]-=a[t];
            e[i][p[i]]+=a[t];
        }
        f+=a[t];
    }
    return f;
}
int main()
{
    while(~scanf("%d%d%d",&n,&f,&d))
    {
        memset(e,0,sizeof(e));
        int a,b,num=0,t=n;
        while(t--)
        {
            num++;
            scanf("%d%d",&a,&b);
            int x,y;
            for(int i=1; i<=a; i++)
            {
                scanf("%d",&x);
                e[x][num+f]=1;//食物指向牛
            }
            for(int j=1; j<=b; j++)
            {
                scanf("%d",&y);
                e[num+f+n][y+2*n+f]=1;//牛指向饮料
            }
        }
        for(int i=1; i<=n; i++)//牛指向牛
        {
            e[i+f][i+n+f]=1;
        }
        for(int i=1; i<=f; i++) //源点指向食物
        {
            e[0][i]=1;
        }
        for(int i=1; i<=d; i++) //饮料指向汇点
        {
            e[i+2*n+f][2*n+f+d+1]=1;
        }
//        for(int i=0; i<=n; i++)
//        {
//            for(int j=0; j<=2*n+d+f+1; j++)
//            {
//                printf("%d ",e[i][j]);
//            }
//            printf("\n");
//        }
        printf("%d\n",EK(0,2*n+f+d+1));
    }
}








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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值