[BZOJ1305/Luogu3153][CQOI2009]dance跳舞
题目链接:
看见匹配就上网络流
二分答案\(Mat\),把每个人拆成喜欢和不喜欢两个点。
对于男生,从源点向喜欢点连边,容量为\(Mat\),同时在喜欢和不喜欢之间连边,容量为\(k\)(因为总共只跳\(Mat\)次舞,要把两个点限制在一起)。
对于女生也是类似的反着,不喜欢\(\rightarrow\)喜欢,容量为\(k\),喜欢\(\rightarrow\)汇点,容量为\(Mat\)。
然后对于两个人\((x,y)\),若\(x\)喜欢\(y\),在对于喜欢的点之间,容量为\(1\),否则连不喜欢。
然后用了个\(ISAP\)。
#include <cstdio>
#include <cstring>
inline int Min(int a,int b){return a<b?a:b;}
int n,k,St,Ed;
int Head[205],Next[40005],To[40005],Val[40005],En;
int Dis[205],Pre[205],Cur[205],Cnt[205];
char Suki[55][55];
inline void Add(int x,int y,int z)
{
Next[++En]=Head[x],To[Head[x]=En]=y,Val[En]=z;
Next[++En]=Head[y],To[Head[y]=En]=x,Val[En]=0;
}
int Augment()
{
int Res=0x3f3f3f3f;
for(int x=Ed;x!=St;x=To[Pre[x]^1])Res=Min(Res,Val[Pre[x]]);
for(int x=Ed,i;x!=St;x=To[i^1])Val[i=Pre[x]]-=Res,Val[i^1]+=Res;
return Res;
}
int ISAP()
{
memset(Dis,0,sizeof Dis);
memcpy(Cur,Head,sizeof Cur);
memset(Cnt,0,sizeof Cnt);
Cnt[0]=Ed;
int Flow=0,x=St;
while(Dis[St]<Ed)
{
if(x==Ed)Flow+=Augment(),x=St;
bool Flag=false;
for(int i=Cur[x],y;i;i=Next[i])
if(Val[i]&&Dis[y=To[i]]+1==Dis[x])
Flag=true,Cur[x]=Pre[y]=i,x=y,i=0;
if(Flag)continue;
int Wd=Ed-1;
for(int i=Head[x];i;i=Next[i])
if(Val[i])Wd=Min(Wd,Dis[To[i]]);
if(!--Cnt[Dis[x]])break;
++Cnt[Dis[x]=Wd+1],Cur[x]=Head[x];
if(x!=St)x=To[Pre[x]^1];
}
return Flow;
}
void Build(int Tar)
{
memset(Head,0,sizeof Head),En=1;
St=n<<2|1,Ed=St+1;//源点汇点
for(int i=1;i<=n;++i)
{//[1,n]男生喜欢,[n+1,n*2]男生不喜欢
Add(St,i,Tar),Add(i,n+i,k);
Add((n<<1)+i,Ed,Tar),Add(n*3+i,(n<<1)+i,k);
}//[n*2+1,n*3]女生喜欢,[n*3+1,n*4]女生不喜欢
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(Suki[i][j]=='Y')
Add(i,(n<<1)+j,1);
else
Add(n+i,n*3+j,1);
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)scanf("%s",Suki[i]+1);
int l=0,r=n+1;
while(l<r)
{
int Mid=(l+r)>>1;
Build(Mid);
if(ISAP()==n*Mid)l=Mid+1;
else r=Mid;
}
printf("%d\n",l-1);
return 0;
}