Description

给定一张航空图,图中顶点代表城市,边代表2城市间的直通航线。现要求找出一条满足下述限制条件的且途经城市最多的旅行路线。
(1)从最西端城市出发,单向从西向东途经若干城市到达最东端城市,然后再单向从东向西飞回起点(可途经若干城市)。
(2)除起点城市外,任何城市只能访问1次。

编程任务:
对于给定的航空图,试设计一个算法找出一条满足要求的最佳航空旅行路线。

由于本OJ无Special Judge , 所以只需要输出最多经过的城市数

Input

第1 行有2个正整数N 和V,N 表示城市数,N<100,V 表示直飞航线数。
接下来的N行中每一行是一个城市名,可乘飞机访问这些城市。
城市名出现的顺序是从西向东。也就是说,设i,j 是城市表列中城市出现的顺序,当i>j 时,表示城市i 在城市j 的东边,而且不会有2 个城市在同一条经线上。城市名是一个长度不超过15 的字符串,串中的字符可以是字母或阿拉伯数字。例如,AGR34或BEL4。
再接下来的V 行中,每行有2 个城市名,中间用空格隔开,如 city1 city2 表示city1到city2 有一条直通航线,从city2 到city1 也有一条直通航线。

Output

程序运行结束时,将最佳航空旅行路线输出。
/*
第1 行是旅行路线中所访问的城市总数M。
接下来的M+1 行是旅行路线的城市名,每行写1 个城市名。首
先是出发城市名,然后按访问顺序列出其它城市名。注意,最后1行(终点城市)的城市名必然是出发城市名。如果问题无解,则输出“No Solution!”。
*/
一行,包含一个整数或字符串,表示最多经过的城市数,如果无解,则输出"No Solution!"(不包含引号)

Sample Input

8 9
Vancouver
Yellowknife
Edmonton
Calgary
Winnipeg
Toronto
Montreal
Halifax
Vancouver Edmonton
Vancouver Calgary
Calgary Winnipeg
Winnipeg Toronto
Toronto Halifax
Montreal Halifax
Edmonton Montreal
Edmonton Yellowknife
Edmonton Calgary

Sample Output

7
/*
Vancouver
Edmonton
Montreal
Halifax
Toronto
Winnipeg
Calgary
Vancouver
*/


这个题,网上有很多的题解和解释啦,我还是希望提出自己的想法。

首先,分类是:最大费用最大流

求最大费用,我使用kuangbin模板,把所有的费用值取成负数,然后调用最小费用最大流的模板,求得结果之后对cost值取相反数


这个题运用的建模思想是:最长不相交路径

因为要求的是从1到n的两条不相交的路径

相当于最大流已经求出来了(必须是2,如果不是2,那么无解)


那么,我们在添加边的时候,需要拆点,把每个点拆成(ia)(ib)

除了1和n的任意点,添加边(ia,ib,1,-1)代表容量为1,单位费用为1

1和n添加边(ia,ib,2,-1)代表容量为2,单位费用为1

然后,可以直接到的边:(边输入边处理)

从小值的点添加边到大值的点:添加边(i+n,j,1,0)

但是如果i=1,j=n,那么需要添加的是(i+n,j,2,0)

代表是拆边之后的转移了


每条航线都是自西向东,本题可以转化为求航线图中从1到N两条不相交的路径,
使得路径长度之和最大。转化为网络流模型,就是找两条最长的增广路。
由于每个城市只能访问一次,要把城市拆成两个点,之间连接一条容量为1的边,费用设为1。
因为要找两条路径,所以起始点和终点内部的边容量要设为2。
那么费用流值-2就是两条路径长度之和,为什么减2,因为有两条容量为2的边多算了1的费用。
求最大费用最大流后,如果(<1.a>,<1.b>)不是满流,
那么我们找到的路径不够2条(可能是1条,也可能0条),所以无解


如何求转移的路径?!

这个问题想了很久,发现可以延续前几个题的思路,用深搜

因为这个题的生成路径必然是个环,我们可以从1点开始用深搜,如果遇到的点是拆点之前的ia,那么需要输出,继续搜索;如果遇到的ib,那么不能输出,继续搜索

这样搜索一圈,一定是从起点走到了终点,然后到了起点的前一个点(因为起点已经访问过了,所以必然会退出搜索)

然后在搜索外面再次输出一次起点就好


题意很好理解:但是想要转换模型是很难的:求最长两条不相交路径,用最大费用最大流解决

因为:题目中说了:流量一定是2

那么,我们的费用越大,说明走过的路径越长


把第i个城市拆分成两个顶点<i.a>,<i.b>。

1、对于每个城市i,连接(<i.a>,<i.b>)一条容量为1,费用为1的有向边,特殊地(<1.a>,<1.b>)和(<N.a>,<N.b>)容量设为2。
2、如果城市i,j(j>i)之间有航线,从<i.b>到<j.a>连接一条容量为1,费用为0的有向边。

求源<1.a>到汇<N.b>的最大费用最大流。如果(<1.a>,<1.b>)不是满流,那么无解。否则存在解,即为最大费用最大流量 - 2


对于,如何找最优的路径输出其实是很简单的

写一个dfs就好了啊

因为每个点的流量最大为2,也就是说最多一进一出

那么从起点开始往下搜索就好,如果碰到的节点编号<=n,那么就输出(拆点前的X集合的点)

否则不输出,继续搜索,这样dfs到最后一个点之后,肯定是回到起点了的



代码如下:

#include<bits/stdc++.h>
using namespace std;

const int maxn=10000;
const int maxm=300000;
const int inf=0x3f3f3f3f;

struct Edge{
	int to,nxt,cap,flow,cost;
}edge[maxm];

char str[maxn][50],st[50];
int Head[maxn],tol;
int pre[maxn];
int dis[maxn];
int n,m,s,t,tot;
bool vis[maxn];

void init(){
	tol=0;
	memset(Head,-1,sizeof(Head));
}

void addedge(int u,int v,int cap,int cost){
	edge[tol].to=v;
	edge[tol].cap=cap;
	edge[tol].cost=cost;
	edge[tol].flow=0;
	edge[tol].nxt=Head[u];
	Head[u]=tol++;
	edge[tol].to=u;
	edge[tol].cap=0;
	edge[tol].cost=-cost;
	edge[tol].flow=0;
	edge[tol].nxt=Head[v];
	Head[v]=tol++;
}

bool spfa(int s,int t){
	queue<int> q;
	for(int i=0;i<=tot+1;i++){
		dis[i]=inf;
		vis[i]=false;
		pre[i]=-1;
	}
	dis[s]=0;
	vis[s]=true;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=false;
		for(int i=Head[u];i!=-1;i=edge[i].nxt){
			int v=edge[i].to;
			if (edge[i].cap>edge[i].flow&&dis[v]>dis[u]+edge[i].cost){
				dis[v]=dis[u]+edge[i].cost;
				pre[v]=i;
				if (!vis[v]){
					vis[v]=true;
					q.push(v);
				}
			}
		}
	}
	if (pre[t]==-1) return false;
	return true;
}

int minCostMaxflow(int s,int t,int &cost){
	int flow=0;
	cost=0;
	while(spfa(s,t)){
		int Min=inf;
		for(int i=pre[t];i!=-1;i=pre[edge[i^1].to])
			if (Min>edge[i].cap-edge[i].flow)
				Min=edge[i].cap-edge[i].flow;
		for(int i=pre[t];i!=-1;i=pre[edge[i^1].to]){
			edge[i].flow+=Min;
			edge[i^1].flow-=Min;
			cost+=edge[i].cost*Min;
		}
		flow+=Min;
	}
	return flow;
}

void dfs(int u){
	if (u<=n&&!vis[u]) cout<<str[u]<<endl;
	vis[u]=true;
	for(int i=Head[u];i!=-1;i=edge[i].nxt){
		int v=edge[i].to;
		if (edge[i].flow&&!vis[v]) dfs(v);
	}
	return;
}

int main(){
	//freopen("input.txt","r",stdin);
	while(scanf("%d%d",&n,&m)!=EOF){
		init();
		for(int i=1;i<=n;i++) scanf("%s",str[i]);
		for(int i=2;i<n;i++) addedge(i,i+n,1,-1);
		addedge(1,1+n,2,-1);
		addedge(n,n+n,2,-1);
		s=1;
		t=n+n;
		tot=n+n;
		for(int i=1;i<=m;i++){
			int pos1,pos2;
			scanf("%s",st);
			for(int j=1;j<=n;j++)
				if (strcmp(str[j],st)==0){
					pos1=j;
					break;
				}
			scanf("%s",st);
			for(int j=1;j<=n;j++)
				if (strcmp(str[j],st)==0){
					pos2=j;
					break;
				}
			if (pos1>pos2) swap(pos1,pos2);
			addedge(pos1+n,pos2,1,0);
			if (pos1==1&&pos2==n) addedge(n+1,n,2,0);
		}
		int Cost,Maxflow;
		Maxflow=minCostMaxflow(s,t,Cost);
		if (Maxflow<2){
			//printf("Maxflow: %d\n",Maxflow);
			puts("No Solution!");
		}
		else{
			printf("%d\n",-Cost-2);
			memset(vis,false,sizeof(vis));
			dfs(1);
			cout<<str[1]<<endl;
		}
	}
	return 0;
}