ACM-ICPC 2017 Asia Qingdao Our Journey of Xian Ends

链接:
https://vjudge.net/problem/计蒜客-A1429
这个题的题意很长,概括一下要点:
有若干个机场,它们的名字仅有英文字母组成,且不超过10位。这些机场一定包含"Xian","Qingdao","Pudong","Hongqiao"。其中"Pudong"和"Hongqiao"之间有一条双向的免费铁路,你可以在这两个机场之间任意穿梭。有若干双向航线,双向飞行的费用一致,你只能沿着航线飞或者坐火车。
你从"Xian"出发,到达"Pudong"或"Hongqiao"(这个或是逻辑or)。然后再飞到"Qingdao",之后到"Pudong",最后坐飞机出国,问直到出国前最少花费多少?
注意务必按照顺序旅行。
你不能在一个机场起飞超过1次,也不能在一个机场降落超过1次,但这不意味着你不能经过一个机场2次。
你如果在"Pudong","Hongqiao"以外的机场,你还没有到达最终目的地"Pudong",你也不能坐火车,所以只能继续起飞。而如果你又回到了这个机场,你还需要再起飞,这样就超过1次起飞次数了。所以"Pudong","Hongqiao"以外的机场只能经过一次。
"Pudong","Hongqiao"比较特殊,它们中间有免费铁路相连,而且"Pudong"是不允许在出国前起飞的,因为你还要从这里起飞出国。
显然,在"Pudong"和"Hongqiao"之间反复横跳没有意义,我们不会连续的在两个机场间折返。
你可以从"Xian"出发,到达"Hongqiao",然后飞到"Qingdao",最后飞到"Pudong"。
你可以从"Xian"出发,到达"Hongqiao",然后坐火车到"Pudong",然后飞"Qingdao",但是你不能在"Pudong"起飞,所以这个路线有问题。
你可以从"Xian"出发,到达"Pudong",然后飞"Qingdao",但是你不能在"Pudong"起飞,所以这个路线有问题。。
你可以从"Xian"出发,到达"Pudong",然后坐火车去"Hongqiao",然后飞到"Qingdao",之后飞到"Hongqiao",然后坐火车去"Pudong"。

所以综上,只有2条路线:
Xian-Hongqiao-Qingdao-Pudong
Xian-Pudong-Hongqiao-Qingdao-Hongqiao-Pudong

这个题不能用最短路做,因为最短路很难限制你路径上点的顺序。

这路要介绍一个思想:
假如题目给定了一个无向图,要并求你从A出发,经过B,到达C,且每个点只能经过一次。你可以把这个A-B-C的路径拆分,认为是A-B,B-C两条路径。又因为是无向图,所以又可以认为是A-B,C-B两条路径。你可以从源点向A,C引1单位流量,从B点向汇点引出2单位流量,并且把每个点的流量(除了端点A,B,C)限制为1,不设边容量的限制,边的费用设置为边权,跑一个最小费用最大流即可。假如最终流量是2,那么就有解,解就是费用,否则无解。流可以直观的想象成路径,如果最终流量是2,那么从A-B,C-B必然存在不相交的路径。
但是如果题目给定了一个无向图,要并求你从A出发,经过B,再经过C,最终到达D,且每个点只能经过一次,这就比较难搞了。
如果还用路径拆分的办法,把A-B-C-D分成A-B,C-B,C-D,然后从源点向A引1单位流量,从源点向C引2单位流量,从B点向汇点引出2单位流量,从D点向汇点引出1单位流量。这样,如果总流量是3,有可能是A-B,C-B,C-D这样的3条路径,也有可能是A-D,C-B,C-B这样的3条路径,那很显然你不好区分这两种情况。
但是看回题目,你的路线是Xian-Hongqiao-Qingdao-Pudong或Xian-Pudong-Hongqiao-Qingdao-Hongqiao-Pudong,计费的路径简写为A-B-C-D或A-D,B-C,C-B
这意味着你不需要区分这两种路径的区别。

除了上面的4个机场,每个机场拆成ar(到达)和de(起飞)两个点,从ar->de连一条容量为1费用为0的边。航线x-y需要从x的de到y的ar连边,从y的de到x的ar离岸边,费用都是边权。从源点向"Xian"连一条容量为1的边,从源点向"Qingdao"连一条容量为2的边,从"Hongqiao"向汇点连一条容量为2的边,从"Pudong"向汇点连一条容量为1的边。跑最小费用最大流,若流量为3,则费用就是解,反之无解。

在输入时,给的都是字符串,你可以用string存储,并用map<string,int>给字符串编号,这样方便连边。建议把"Xian","Hongqiao","Qingdao","Pudong"先映射为1,2,3,4,方便处理。不用map映射也可以排序再二分查找或者建trie树查找,但是比较麻烦。

由于边数m<=10000,流量是3,考虑一般出题人不会刻意卡spfa,跑一次最小费用最大流的时间开销不会超过1e6这个数量级,一共有160组样例,4s应该能跑过。
代码如下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstring>
#include<map>
using namespace std;
const int MAXN=21000,MAXM=61000,inf=1000000000;
struct edge
{
	int y,next,rev,c,cost;
}graph[MAXM];
struct pii
{
	int cost,flow;
};
pii mp(int cost,int flow)
{
	pii re;
	re.cost=cost;
	re.flow=flow;
	return re;
}
int head[MAXN];
int add(int num,int x,int y,int c,int cost)
{
//	printf("%d %d %d %d
",x,y,c,cost);
	int e=num*2-1,rev=num*2;
	graph[e].y=y;
	graph[e].c=c;
	graph[e].cost=cost;
	graph[e].rev=rev;
	graph[e].next=head[x];
	head[x]=e;
	
	graph[rev].y=x;
	graph[rev].c=0;
	graph[rev].cost=-cost;
	graph[rev].rev=e;
	graph[rev].next=head[y];
	head[y]=rev;
}
int pre_v[MAXN],pre_e[MAXN];
int dis[MAXN],cnt_qu[MAXN];
bool in_qu[MAXN];
bool cmp(int x,int y,bool minn)//minn==1,<;minn==0,>
{
	if(minn)return x<y;
	return x>y;
}
queue<int> q;
void push(int x)
{
	cnt_qu[x]++;
	in_qu[x]=1;
	q.push(x);
}
int pop()
{
	int x=q.front();
	in_qu[x]=0;
	q.pop();
	return x;
}
bool spfa(int s,int t,int all,bool minn)
{
	int init=inf;
	if(minn==0)init=-inf;
	
	for(int i=1;i<=all;i++)dis[i]=init;
	for(int i=1;i<=all;i++)in_qu[i]=0;
	for(int i=1;i<=all;i++)cnt_qu[i]=0;
	while(!q.empty())q.pop();
	
	dis[s]=0;
	push(s);
	while(!q.empty())
	{
		int now=pop();
		//printf("%d %d %d
",now,dis[now],head[now]);
		for(int k=head[now];k!=-1;k=graph[k].next)
		{
			int y=graph[k].y;
			//printf("%d ",y);
			if(graph[k].c>0)
			{
				if(cmp(dis[now]+graph[k].cost,dis[y],minn))
				{
					dis[y]=dis[now]+graph[k].cost;
					pre_e[y]=k;
					pre_v[y]=now;
					if(!in_qu[y])push(y);
					if(cnt_qu[y]>=all)
					{
						return 0;
					}
				}
			}
		}
	}
	return dis[t]!=init;
}
pii min_cost_flow(int s,int t,int all,bool minn)
{
	int cost=0,flow=0;
	while(spfa(s,t,all,minn))
	{
		int now=t;
		int f=inf;
		while(now!=s)
		{
			int e=pre_e[now];
			f=min(f,graph[e].c);
			now=pre_v[now];
		}
		flow+=f;
		now=t;
		while(now!=s)
		{
			int e=pre_e[now];
			int rev=graph[e].rev;
			graph[e].c-=f;
			graph[rev].c+=f;
			now=pre_v[now];
		}
		cost+=f*dis[t];
	}
	return mp(cost,flow);
}
map<string,int> name;
int de(int x)
{
	return x*2;
}
int ar(int x)
{
	return x*2-1;
}
int cnt_e=0;
int solve(int n)
{
	int S=2*n+1,T=2*n+2;
	for(int i=5;i<=n;i++)
	{
		add(++cnt_e,ar(i),de(i),1,0);
	}
	add(++cnt_e,ar(1),de(1),2,0);
	add(++cnt_e,S,ar(1),2,0);
	
	add(++cnt_e,ar(2),de(2),1,0);
	add(++cnt_e,S,ar(2),1,0);
	
	add(++cnt_e,ar(3),de(3),2,0);
	add(++cnt_e,de(3),T,2,0);
	
	add(++cnt_e,ar(4),de(4),1,0);
	add(++cnt_e,de(4),T,1,0);
	
	pii ans=min_cost_flow(S,T,T,1);
	if(ans.flow<3)return -1;
	return ans.cost;
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int num=0;
		cnt_e=0;
		name.clear();
		int n;
		scanf("%d",&n);
		memset(head,-1,sizeof(head));
		name["Hongqiao"]=1;
		name["Pudong"]=2;
		name["Qingdao"]=3;
		name["Xian"]=4;
		num=4;
		for(int i=1;i<=n;i++)
		{
			string str1,str2;
			int cost;
			cin>>str1>>str2>>cost;
			if(name[str1]==0)name[str1]=++num;
			if(name[str2]==0)name[str2]=++num;
			add(++cnt_e,de(name[str1]),ar(name[str2]),inf,cost);
			add(++cnt_e,de(name[str2]),ar(name[str1]),inf,cost);
		}
		int ans=solve(num);
		printf("%d
",ans);
	}
}
原文地址:https://www.cnblogs.com/ssdfzhyf/p/14474155.html