[最大流]BZOJ 1711: [Usaco2007 Open]Dining吃饭 题解

本题有权限……

题目描述

农夫JOHN为牛们做了很好的食品,但是牛吃饭很挑食. 每一头牛只喜欢吃一些食品和饮料而别的一概不吃.虽然他不一定能把所有牛喂饱,他还是想让尽可能多的牛吃到他们喜欢的食品和饮料. 农夫JOHN做了F (1 <= F <= 100) 种食品并准备了D (1 <= D <= 100) 种饮料. 他的N (1 <= N <= 100)头牛都以决定了是否愿意吃某种食物和喝某种饮料. 农夫JOHN想给每一头牛一种食品和一种饮料,使得尽可能多的牛得到喜欢的食物和饮料. 每一件食物和饮料只能由一头牛来用. 例如如果食物2被一头牛吃掉了,没有别的牛能吃食物2.

输入格式

  • 第一行: 三个数: N, F, 和 D

  • 第2..N+1行: 每一行由两个数开始F_i 和 D_i, 分别是第i 头牛可以吃的食品数和可以喝的饮料数.下F_i个整数是第i头牛可以吃的食品号,再下面的D_i个整数是第i头牛可以喝的饮料号码.

输出格式

  • 第一行: 一个整数,最多可以喂饱的牛数.

输入样例

4 3 3
2 2 1 2 3 1
2 2 2 3 1 2
2 2 1 3 1 2
2 1 1 3 3

输出样例

3

样例解释:

牛 1: 食品从 {1,2}, 饮料从 {1,2} 中选
牛 2: 食品从 {2,3}, 饮料从 {1,2} 中选
牛 3: 食品从 {1,3}, 饮料从 {1,2} 中选
牛 4: 食品从 {1,3}, 饮料从 {3} 中选

一个方案是:
Cow 1: 不吃
Cow 2: 食品 #2, 饮料 #2
Cow 3: 食品 #1, 饮料 #1
Cow 4: 食品 #3, 饮料 #3

用鸽笼定理可以推出没有更好的解 (一共只有3总食品和饮料).当然,别的数据会更难.

解题分析

这难道是传说中的三分图匹配?算了,我也不会……

可不可以转化成两个独立的二分图匹配?也不行,自己手造一个数据分分钟卡掉。

那么还要怎么做?对于二分图匹配,我们有用最大流的方法来解决,那么这道题应该也可以对题目进行网络流建模再解决,事实上也正是如此。

所以这道题的关键就成了建模……

初始想法是先一列牛再一列食物再一列饮料,但是食物和饮料如何连边?然后就是一个神奇的思路——把牛放中间,这样的话建边就很容易……吗?

这样建图是有问题滴!因为这样一头牛有可能有多个食物的流量进入这个“牛”点,然后又流向多个饮料点最后流到汇点,但这样违反了一头牛只能选一种食物和一种饮料的规则,所以解决方案就是——拆点(这是网络流常用套路之一)。

对于每头牛拆成两个点i1和i2,并额外加一个源点和一个汇点(同样是网络流常用套路之一)那么首先源点向每个食物建一条边,然后食物向想要吃它的牛对应的点i1建一条边,然后i1向i2建一条边,然后i2向想要喝的饮料连一条边,最后所有饮料向汇点连一条边(不得不说太神奇了),所有边的流量限制均为1。

然后Dinic刷最大流就行了,EK貌似不行,e有40000+,EK难以承受,其实DinicO( n2m n 2 ∗ m )的算法对于e40000+,n400的大小貌似也不行,但是貌似这种所有边限制均为1的图Dinic复杂度会低很多,蓝书上好像有,我不记得了……(查过说是 O(min(n23,m12)m) O ( m i n ( n 2 3 , m 1 2 ) m ) )

复杂度

时间:O( min(n23,m12)m m i n ( n 2 3 , m 1 2 ) m )(Dinic的玄学复杂度…)
空间:O(e)

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 405
#define maxe 50005
using namespace std;
const int INF=(((1<<30)-1)<<1)+1;
int n,m,k,s,t,tot,lnk[maxn],nxt[maxe],son[maxe],que[maxn],dst[maxn],cap[maxe],flow[maxe],lst[maxn];
bool vs[maxn];
inline void readi(int &x){
    x=0; char ch=getchar();
    while ('0'>ch||ch>'9') ch=getchar();
    while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
}
void _add(int x,int y,int z){
    tot++; son[tot]=y; nxt[tot]=lnk[x]; cap[tot]=z; flow[tot]=0; lnk[x]=tot;
    tot++; son[tot]=x; nxt[tot]=lnk[y]; cap[tot]=0; flow[tot]=0; lnk[y]=tot;
}
void _init()
{
    freopen("dingin.in","r",stdin);
    freopen("dingin.out","w",stdout);
    memset(lnk,0,sizeof(lnk));
    memset(nxt,0,sizeof(nxt));
    readi(n); readi(m); readi(k); s=2*n+m+k+1; t=s+1; tot=1;
    for (int i=1;i<=n;i++) _add(i,i+n,1);
    for (int i=1;i<=m;i++) _add(s,2*n+i,1);
    for (int i=1;i<=k;i++) _add(2*n+m+i,t,1);
    for (int i=1,ka,kb,x;i<=n;i++){
        readi(ka); readi(kb);
        for (int j=1;j<=ka;j++) {readi(x); _add(2*n+x,i,1);}
        for (int j=1;j<=kb;j++) {readi(x); _add(i+n,2*n+m+x,1);}
    }
}
bool _bfs(){    
    memset(vs,0,sizeof(vs));
    memset(dst,0,sizeof(dst));
    int hed=0,til=1; que[1]=s; vs[s]=1; dst[s]=0;
    while (hed!=til){
        int x=que[++hed];
        for (int j=lnk[x];j;j=nxt[j])
            if (!vs[son[j]]&&cap[j]>flow[j]) {que[++til]=son[j]; dst[son[j]]=dst[x]+1; vs[son[j]]=1;}
    }
    return vs[t];
}
int _dfs(int x,int now){
    if (x==t||now==0) return now; int tem=0;
    for (int j=lst[x];j;lst[x]=j=nxt[j])
        if (dst[x]+1==dst[son[j]]){
            int ew=_dfs(son[j],min(now,cap[j]-flow[j]));
            if (ew){
                tem+=ew; now-=ew; flow[j]+=ew; flow[j^1]-=ew;
                if (!now) break;
            }
        }
    return tem;
}
int FDinic(){
    int ans=0;
    while (_bfs()){
        for (int i=1;i<=t;i++) lst[i]=lnk[i];
        ans+=_dfs(s,INF);
    }
    return ans;
}
void _solve(){
    int ans=FDinic();
    printf("%d",ans);
}
int main()
{
    _init();
    _solve();
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值