P2756 飞行员配对方案问题

发布时间 2023-08-26 16:23:44作者: 傻阙的缺

传送门

考虑网络流:

源点分别向\(1\)\(m\)连一条流量为\(1\)的边,表示每个外籍飞行员最多有一个贡献。

\(m+1\)\(n\)都向汇点连一条流量为\(1\)的边,表示每个英国飞行员最多一个贡献

\(u\)\(v\)连一条流量为\(1\)的边,表示\(u\)号外籍飞行员和\(v\)号英国飞行员可以成为搭档

网络流算法求最大流即可

考虑怎么找方案:

思考\(Dinic\)计算答案的方式,可以发现每一个外籍飞行员连向的一定的是英国飞行员,\(dfs\)时记下\(pre\),到达汇点时求一下方案即可

理解:反向边对于我们来说是一个反悔操作,然后我们若手动模拟一下\(Dinic\)算法会发现:当我们走反向边时,我们一定是从英国飞行员走到外籍飞行员。什么意思呢?就是原来的这对搭档要拆开,英国飞行员要与正向边连向他的人合作,外籍飞行员要与他连向的人合作。因为外籍飞行员没有连向汇点,所以外籍飞行员一定会连向英国飞行员,他们做搭档即可(细节自己看代码)。

上代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=100000,INF=1e15;
ll m,n,u,v;
struct jgt
{
	ll to;
	ll w;
	ll ne;
}e[N];
ll h[N],idx=1;
ll st,en;
void add(ll a,ll b,ll c)
{
	e[++idx].to=b;
	e[idx].w=c;
	e[idx].ne=h[a];
	h[a]=idx;
	e[++idx].to=a;
	e[idx].w=0;
	e[idx].ne=h[b];
	h[b]=idx;
}
ll dis[N];
ll now[N],ans;
ll pre[N];
ll le[N],ri;
bool bfs()
{
	for(ll i=st;i<=en;i++) dis[i]=INF;
	queue<ll> q;
	q.push(st);
	dis[st]=0;
	now[st]=h[st];
	while(!q.empty())
	{
		ll wz=q.front();
		q.pop();
		for(ll i=h[wz];i;i=e[i].ne)
		{
			ll j=e[i].to;
			if(e[i].w&&dis[j]==INF)
			{
				dis[j]=dis[wz]+1;
				now[j]=h[j];
				q.push(j);
				if(j==en) return true;
			}
		}
	}
	return false;
}
void xg()
{
	for(ll i=en;i!=st;i=pre[i])
	{
		if(i>=m+1&&i<=m+n) ri=i;
		if(i>=1&&i<=m) le[i]=ri;
	}
}
ll dfs(ll wz,ll sum)
{
	if(wz==en)
	{
		xg();
		return sum;
	}
	ll re=0,k;
	for(ll i=now[wz];i&&sum;i=e[i].ne)
	{
		now[wz]=i;
		ll j=e[i].to;
		if(e[i].w&&dis[j]==dis[wz]+1)
		{
			pre[j]=wz;
			k=dfs(j,min(sum,e[i].w));
			re+=k;
			sum-=k;
			e[i].w-=k;
			e[i^1].w+=k;
		}
	}
	return re;
}
int main()
{
	scanf("%lld %lld",&m,&n);
	st=0;
	en=n+1;
	for(ll i=1;i<=m;i++)
	add(st,i,1);
	for(ll i=m+1;i<=n;i++)
	add(i,en,1);
	while(1)
	{
		scanf("%lld %lld",&u,&v);
		if(u==-1&&v==-1) break;
		add(u,v,1);
	}
	while(bfs()) ans+=dfs(st,INF);
	printf("%lld\n",ans);
	for(ll i=1;i<=m;i++)
	if(le[i]!=0) printf("%lld %lld\n",i,le[i]); 
	return 0;
}