P2055 [ZJOI2009]假期的宿舍(匈牙利算法或者最大流)

本文探讨了一种利用二分图最大匹配和网络流算法解决学生住宿分配问题的方法。面对学校放假期间的学生住宿需求,文章通过构建人与床位的二分图,并运用最大匹配算法找到最优住宿方案。同时,还介绍了使用网络流算法的另一种解决方案,通过建立源点、汇点和相应的边来模拟问题,最终比较最大流与需求人数判断是否可行。

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

题目链接
题意:
学校放假了,一部分学生回家一部分留校,然后有一些外校生来找他们的朋友玩。怎么安排住宿是个问题,他们所有人都有一个毛病只想睡朋友或者自己的床。问是否有一个合理的住宿方案?

看到这个很容易会想到二分图最大匹配,先将整个图分成两部分:人和床,求人和床的最大匹配。首先是本校生他才会有床,床的编号就是他的编号,然后对于每一个外校生找最大匹配。对于留校的本校生先让他睡他自己的床,还需要给他与他的床之间连一条边,刚开始想着留校生已经匹配好床了就没连结果wa了一个点,可能会存在留校生先把床让给了朋友,然后接下来的匹配他还得必须回到自己的床才能行,所以必须要连边。输入有些麻烦仔细处理一下然后就可以套板子了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=60;
int t,n,a[N],b[N],c[N][N],d[N];//d[i]:第i张床睡的谁
bool st[N];
bool find(int u)
{
	for(int i=1;i<=n;i++)
	{
		if(!c[u][i]) continue;
		int v=i;
		if(!st[v]&&a[i])
		{
			st[v]=1;
			if(!d[v]||find(d[v]))
			{
				d[v]=u;
				return 1;
			}
		}
	}
	return 0;
}
int main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		for(int i=1;i<=n;i++) cin>>a[i];
		for(int i=1;i<=n;i++) cin>>b[i];
		for(int i=1;i<=n;i++) 
			for(int j=1;j<=n;j++)
				cin>>c[i][j];
		memset(d,0,sizeof d);
		for(int i=1;i<=n;i++) 
		{
			c[i][i]=a[i];//自己与床建边
			if(a[i]&&!b[i]) d[i]=i;//自己先睡自己的床
		}
		bool flag=1;
		for(int i=1;i<=n;i++)
			if(!a[i])
			{
				memset(st,0,sizeof st);//记得清零,标记该次匹配找过谁
				if(!find(i))
				{
					flag=0;
					break;
				}
			}
		cout<<(!flag?"T_T":"^_^")<<endl;
	}
	return 0;
}

下面是大佬的网络流思路:
所有的点被分为人和床(第i张床的编号定为i+n),如果i与j认识就再i与j+n之间建双向边(正边权为1,逆边权为0,下同),另外网络流都需要一个源点和汇点,分别定义为0和n*2+1(只要是不与其他点冲突的两个点都行)。因为是人要找床嘛,所以源点与所有需要找床的人之间建边,所有床与汇点之间建边,然后求出最大流再与需要找床的人数比较。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=110,M=5010;
int T,n,s,t,a[N],b[N],c[N][N];
int h[N],e[M],ne[M],w[M],idx;
void add(int a,int b,int c)
{
	e[idx]=b;
	w[idx]=c;
	ne[idx]=h[a];
	h[a]=idx++;
}
int dep[N];
bool bfs()//分层
{
	queue<int> q;
	memset(dep,0,sizeof dep);
	dep[s]=1;
	q.push(s);
	while(q.size())
	{
		int u=q.front();
		q.pop();
		for(int i=h[u];i!=-1;i=ne[i])
		{
			int v=e[i];
			if(dep[v]==0&&w[i]>0)
			{
				dep[v]=dep[u]+1;
				q.push(v);
			}
		}
	}
	return dep[t]==0;
}
int cur[N];
int dfs(int u,int flow)
{
	if(u==t) return flow;
	int sum=0;
	for(int i=cur[u];i!=-1;i=ne[i])
	{
		cur[u]=i;//弧优化
		int v=e[i];
		if(dep[v]==dep[u]+1&&w[i]>0)
		{
			int tmp=dfs(v,min(flow,w[i]));
			flow-=tmp;
			sum+=tmp;
			w[i]-=tmp;
			w[i^1]+=tmp;
			if(flow==0) break;
		}
	}
	return sum;
}
int dinic()
{
	int sum=0;
	while(1)
	{
		if(bfs()) break;
		for(int i=0;i<=t;i++) cur[i]=h[i];
		sum+=dfs(s,0x3f3f3f3f);
	}
	return sum;
}
int main()
{
	cin>>T;
	while(T--)
	{
		cin>>n;
		s=0,t=(n<<1|1);
		int num=0;//别忘了初始化
		memset(h,-1,sizeof h);
		idx=0;//记得清零
		for(int i=1;i<=n;i++) 
		{
			cin>>a[i];
			if(a[i])
			{
				add(i+n,t,1);
				add(t,i+n,0);
			}
		}
		for(int i=1;i<=n;i++) 
		{
			cin>>b[i];
			if(!a[i]||(a[i]&&!b[i]))
			{
				num++;//需要找床的人数,包括留校生
				add(s,i,1);
				add(i,s,0);
			}
		}
		for(int i=1;i<=n;i++) 
			for(int j=1;j<=n;j++)
			{
				cin>>c[i][j];
				if(c[i][j]||(i==j))
				{
					add(i,j+n,1);
					add(j+n,i,0);
				}
			}
		int flow=dinic();
		cout<<((flow<num)?"T_T":"^_^")<<endl;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值