1305: [CQOI2009]dance跳舞

本文介绍了一种结合二分查找与网络流算法解决匹配问题的方法。通过将问题转化为最大流问题,在图中构建特定的边来求解最优匹配方案。适用于解决包含偏好匹配条件的复杂问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:

中文题,自己看吧,题目链接

题解:
二分+网络流。

二分ans,把每个男生女生拆成两个点。

分别表示喜欢和不喜欢,

st向男生喜欢连一条流量为ans的边,

男生喜欢向男生不喜欢连一条流量为k的边,

女生喜欢向ed连一条流量为ans的边,

女生不喜欢向女生喜欢连一条流量为k的边,

男生喜欢向女生喜欢连一条流量为1的边,

男生不喜欢向女生不喜欢连一条流量为1的边,

如果最大流大于等于ans*n则ans合法。

代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=40010;
int n,k,st,ed;
struct node{
	int x,y,z,next,other;
}sa[N*2];int first[N],len=1;
void ins(int x,int y,int z)
{
	len++;sa[len].x=x;sa[len].y=y;sa[len].z=z;
	sa[len].next=first[x];first[x]=len;sa[len].other=len+1;
	
	len++;sa[len].x=y;sa[len].y=x;sa[len].z=0;
	sa[len].next=first[y];first[y]=len;sa[len].other=len-1;
}
int list[N],head,tail,h[N];
bool bt()//宽搜只是为了构建h数组   
{  
    memset(h,0,sizeof(h));h[st]=1; //出发点的层次为1  
    list[1]=st;head=1;tail=2; //宽搜  
    while(head!=tail)  
    {  
        int x=list[head];  
        for(int k=first[x];k!=-1;k=sa[k].next)  
        {  
            int y=sa[k].y;  
            if(  sa[k].z>0 &&  h[y]==0  )//a[k].c>0让多次构图成为可能  
            {  
                // x >y这条边还有流量,并且 y没有访问过  
                h[y]=h[x]+1;   //y作为x的下一层  
                list[tail++]=y;  
            }  
        }  
        head++;  
    }  
    if(h[ed]>0) return true;//如果最后能到ed返回true,否则返回false  
    else return false;  
}  
int findans(int x,int f)//函数值等于:带着“期待流量”f从x出发,最后得到的“实际流量”s。   
{  
    if(x==ed) return f; //x如果是终点,那么当前所带的期待目标流量都可以完成   
    int s=0,t;  
    for(int k=first[x];k!=-1;k=sa[k].next) // 尝试通过所有孩子结点分散任务  
    {  
        int y=sa[k].y;    
        if( sa[k].z>0 && h[y]==(h[x]+1) && s<f )//a[k].c>0表示x->y这条边还有流量,且y是x的下一层,且“实际流量”<“期望流量”还没有满  
        {   
            s+=(  t=findans(  y  , min(sa[k].z,f-s)  )  ); //从y出发,而附带的“期望流量”是多少呢?;   
            sa[k].z-=t;sa[ sa[k].other ].z+=t; //及时修改边的流量,且同时增加反向边的流量  
        }  
    }  
    if(s==0)h[x]=0;//如果x出发没有流量,x从此不可走  
    return s;  
}
char s[52][52];
/*
st 0
1~n 男生1
n+1~2*n 男生2
2*n+1~3*n 女生1
3*n+1~4*n 女生2 
ed 4*n+1
*/
bool check(int x)
{
	len=1;
    memset(first,-1,sizeof(first));
	for(int i=1;i<=n;i++)
	ins(st,i,x);
	for(int i=n+1;i<=2*n;i++)
	ins(i-n,i,k);
	for(int i=2*n+1;i<=3*n;i++)
	ins(i,ed,x);
	for(int i=3*n+1;i<=4*n;i++)
	ins(i,i-n,k);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
		if(s[i][j]=='Y')
		{
			ins(i,j+2*n,1);
		}
		else
		{
			ins(i+n,j+3*n,1);
		}
	}
	int ans=0;
	while(bt())
	{
		ans+=findans(st,100000000);
	}
	if(ans>=x*n) return true;
	return false;
}
int main()
{
	
	scanf("%d%d",&n,&k);
	st=0;ed=4*n+1;
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s[i]+1);
	}
	int l=0,r=n,mid;
	int ans=0;
	while(l<=r)
	{
		mid=(l+r)/2;
		//printf("%d\n",mid);
		if(check(mid)==true) l=mid+1,ans=mid;
		else r=mid-1;
	}
	printf("%d\n",ans);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值