[BZOJ1305/Luogu3153][CQOI2009]dance跳舞

题目链接:

BZOJ1305

Luogu3153

看见匹配就上网络流

二分答案\(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;
}
posted @ 2018-12-30 19:38  LanrTabe  阅读(121)  评论(0编辑  收藏  举报