LOJ #6122. 「网络流 24 题」航空路线问题

仅简单介绍下最大费用最大流做法:
1.我们可以把题意转换为:从1城市走两次到n城市,每个城市只能经过一次,最多可经过多少个城市。
2.由于一个中间城市只能到达一次,所以对于中间城市,拆成入点和出点,流量为1;对于1和n,流量为2。
3.由于需要求最多经过的城市数,所以对于每一个城市的入点和拆点之间,可以再连一条费用为1的边,做最大费用流。
4.对于有航班的两个城市,连流量为1,费用为0的边。
5.输出路径,则是考虑一下最大流跑完后,残余网络的性质即可dfs得到。
6.对于有1直接到n的情况,需要做一个特判。
#include <bits/stdc++.h>
using namespace std;
const int N=105,M=1e4+5,inf=2e9;
int n,m,s,t,mincost,ans;
string s1,s2;
map<string,int>id;
string name[N];
int d[N*2];
bool vis[N*2],ff[N*2];
int cnt=1,head[N*2];
struct edge{int next,from,to,w,dis;}e[M*3];

inline void add(int u,int v,int w,int dis)
{
	cnt++;
	e[cnt].next=head[u];
	e[cnt].from=u;
	e[cnt].to=v;
	e[cnt].w=w;
	e[cnt].dis=dis;
	head[u]=cnt;	
}
inline void insert(int u,int v,int w,int dis)
{
	add(u,v,w,dis); add(v,u,0,-dis);	
}

queue<int>q;
inline bool spfa()
{
	memset(d,-60,sizeof(d));
	int maxn=d[0];
	memset(vis,false,sizeof(vis));
	d[s]=0; vis[s]=true; q.push(s);
	while (q.size())
	{
		int u=q.front(); q.pop();
		vis[u]=false;
		for (register int i=head[u]; i; i=e[i].next)
		if (e[i].w && d[e[i].to]<d[u]+e[i].dis)
		{
			d[e[i].to]=d[u]+e[i].dis;
			if (!vis[e[i].to]) q.push(e[i].to),vis[e[i].to]=true;
		}
	}
	if (d[t]!=maxn) return true;
	return false;
}

int dfs(int u,int flow)
{
	if (u==t) return flow;
	int remain=flow;
	ff[u]=true;
	for (register int i=head[u]; i; i=e[i].next)
	if ((!ff[e[i].to] || e[i].to==t) && e[i].w && d[e[i].to]==d[u]+e[i].dis)
	{
		int k=dfs(e[i].to,min(e[i].w,remain));
		if (!k) {d[e[i].to]=-1; continue;}
		e[i].w-=k; e[i^1].w+=k; 
		mincost+=e[i].dis*k;
		remain-=k;
		if (!remain) break;	
	}
	return flow-remain;
}

inline int dinic()
{
	int ans=0;
	while (spfa())
	{
		memset(ff,false,sizeof(ff));
		ans+=dfs(s,inf);	
	}
	return ans;
}

void dfs(int u)
{
	cout<<name[u]<<endl;
	vis[u]=true;
	for (register int i=head[u+n]; i; i=e[i].next)
	if (!e[i].w && !vis[e[i].to] && e[i].to<=n)
	{
		dfs(e[i].to);
		break;
	}
}

void dfs2(int u)
{
	vis[u]=true;
	for (register int i=head[u+n]; i; i=e[i].next)
	if (!e[i].w && !vis[e[i].to] && e[i].to<=n)
	{
		dfs2(e[i].to);
		break;
	}
	cout<<name[u]<<endl;
}

int main(){
	scanf("%d%d",&n,&m);
	s=1,t=n+n;
	for (register int i=1; i<=n; ++i)
	{
		cin>>s1;
		name[i]=s1;
		id[s1]=i;
		if (i==1 || i==n) insert(i,i+n,2,1);
		else insert(i,i+n,1,1);
	}
	bool jay=false;
	for (register int i=1; i<=m; ++i)
	{
		cin>>s1>>s2;
		if (id[s1]>id[s2]) swap(s1,s2);
		int u=id[s1],v=id[s2];
		insert(u+n,v,1,0);
		if (u==1 && v==n) jay=true;
	}
	ans=dinic();
	//printf("ans=%d\n",ans);
	if (ans==2)
	{
		printf("%d\n",mincost-2);
		memset(vis,false,sizeof(vis));
		dfs(1);
		dfs2(1);
	}
	else if (ans==1 && jay)
	{
		printf("%d\n",mincost);
		memset(vis,false,sizeof(vis));
		dfs(1);
		cout<<name[1]<<endl;
	}	
	else puts("No Solution!");
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值