本题有权限……
题目描述
农夫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(n2∗mn2∗m)的算法对于e40000+,n400的大小貌似也不行,但是貌似这种所有边限制均为1的图Dinic复杂度会低很多,蓝书上好像有,我不记得了……(查过说是O(min(n23,m12)m)O(min(n23,m12)m))
复杂度
时间:O(min(n23,m12)mmin(n23,m12)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;
}