Description
给定一张航空图,图中顶点代表城市,边代表2城市间的直通航线。现要求找出一条满足下述限制条件的且途经城市最多的旅行路线。
(1)从最西端城市出发,单向从西向东途经若干城市到达最东端城市,然后再单向从东向西飞回起点(可途经若干城市)。
(2)除起点城市外,任何城市只能访问1次。
对于给定的航空图,试设计一个算法找出一条满足要求的最佳航空旅行路线。
Input Format
第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 Format
输出最佳航空旅行路线。第1 行是旅行路线中所访问的城市总数M。接下来的M+1 行是旅行路线的城市名,每行写1 个城市名。首先是出发城市名,然后按访问顺序列出其它城市名。注意,最后1行(终点城市)的城市名必然是出发城市名。如果问题无解,则输出“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
分析:
从最西端的城市到最东端的城市,再回到最西端的城市,可以看作是两次从最西端的城市到最东端的城市,就是寻找最长两条不相交路径,用费用流解决。
构图:
(1)因为每个城市只能经过一次,把城市拆成两个点Ai和Bi,Ai向Bi连一条流量为1,费用为0的边。
(2)从源点连向B1,流量为2,费用为0;从An连向汇点,流量为2,费用为0。
(3)从Bi向Aj连一条流量为1,费用为-1的边,表示城市i与城市j相通。如果i = 1, j = n,那么流量为2。
在图上跑一遍最小费用最大流(因为费用变成了负数,如果是正数就是最大费用最大流),然后取相反数就是结果。
如果源点连向B1的边的流量大于0,就说明无法找到第二条增广路,则输出"No Solution!"。
输出答案的时候,根据流量图里的信息,另外构建一个图,表示所有城市之间的联通性,如果Bi到Aj的边流量为0,则城市i和j联通。然后在图上做一次搜索把城市名称输出就好了。
代码:
1 #include <cstdio> 2 #include <cstring> 3 const int maxn = 500; 4 const int maxm = 500000; 5 const int maxq = 5000000; 6 const int inf = 2147483647; 7 char name[110][20], s1[20], s2[20]; 8 int n, m, a, b, s, t, en, el[maxn]; 9 int et[maxm], ep[maxm], ef[maxm], ec[maxm]; 10 int q[maxq], h, r, now, to, pn[maxn], pe[maxn], d[maxn], v[maxn]; 11 inline void ins (int sta, int end, int fl, int co) //邻接表,网络流使用 12 { 13 et[++en] = end; ep[en] = el[sta]; ec[en] = co; ef[en] = fl; el[sta] = en; 14 et[++en] = sta; ep[en] = el[end]; ec[en] = -co; ef[en] = 0; el[end] = en; 15 } 16 int ot[maxm], op[maxm], on, ol[maxn], ov[maxn]; 17 inline void ins2 (int sta, int end) //邻接表2,输出结果时使用 18 { 19 ot[++on] = end; op[on] = ol[sta]; ol[sta] = on; 20 ot[++on] = sta; op[on] = ol[end]; ol[end] = on; 21 } 22 inline int min (int arg0, int arg1) 23 { 24 return arg0 < arg1 ? arg0 : arg1; 25 } 26 bool bfs () //寻找增广路 27 { 28 memset (v, 0, sizeof (v)); 29 memset (d, 63, sizeof (d)); 30 memset (pe, -1, sizeof (pe)); 31 memset (pn, -1, sizeof (pn)); 32 d[q[h = r = 0] = s] = 0, v[t] = 1; 33 while (h <= r) 34 { 35 v[now = q[h++]] = 0; 36 for (int i = el[now]; i; i = ep[i]) 37 { 38 if (d[to = et[i]] > d[now] + ec[i] && ef[i]) 39 { 40 d[to] = d[now] + ec[i]; 41 pn[to] = now; pe[to] = i; 42 if (!v[to]) v[q[++r] = to] = 1; 43 } 44 } 45 } 46 return pe[t] != -1; 47 } 48 void dfs (int x) //Dinic 49 { 50 printf ("%s ", name[x]); ov[x] = 1; 51 for (int i = ol[x]; i; i = op[i]) 52 if (!ov[ot[i]]) dfs (ot[i]); 53 } 54 void work () 55 { 56 int ans = 0, tmp; 57 while (bfs ()) //最小费用最大流 58 { 59 tmp = inf; 60 for (int i = t; i ^ s; i = pn[i]) 61 tmp = min (tmp, ef[pe[i]]); 62 for (int i = t; i ^ s; i = pn[i]) 63 { 64 ef[pe[i]] -= tmp; 65 ef[pe[i] ^ 1] += tmp; 66 } 67 ans -= tmp * d[t]; 68 } 69 if (ef[2]) printf ("No Solution! "); //ef[2]为第一条边的剩余流量,如果还有剩余,则说明没有找到两条增广路 70 else 71 { 72 memset (ov, 0, sizeof (ov)); 73 printf ("%d ", ans); 74 for (int i = n + 1; i < s; i++) 75 for (int j = el[i]; j; j = ep[j]) 76 if (et[j] <= n && et[j] != i - n && ef[j] == 0) 77 ins2 (i - n, et[j]); //构建新图 78 dfs (1); printf("%s ", name[1]); 79 } 80 } 81 int main () 82 { 83 scanf ("%d %d", &n, &m); en = 1; 84 s = n * 2 + 1; t = s + 1; 85 ins (s, 1 + n, 2, 0); ins (n, t, 2, 0); //构图 86 for (int i = 1; i <= n; i++) 87 { 88 scanf ("%s", name[i]); 89 ins (i, i + n, 1, 0); //构图 90 } 91 for (int i = 0; i < m; i++) 92 { 93 scanf ("%s %s", s1, s2); 94 a = b = 0; 95 for (int j = 1; j <= n; j++) 96 { 97 strcmp (name[j], s1) ? 0 : a = j; 98 strcmp (name[j], s2) ? 0 : b = j; 99 } //暴力判定字符串 100 ins (a + n, b, 1, -1); //构图 101 if (a == 1 && b == n) 102 ins (n + 1, n, 1, -1); 103 } 104 work (); 105 }
因为网络上没有找到Special Judge的代码,就自己写了一个(用Cena测评):
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 5 FILE *fscore, *freport, *fstd, *fin, *fout; 6 int cnt[110][110], vis[110]; 7 char name[110][20], s1[20], s2[20]; 8 char res1[20], res2[20]; 9 10 bool Judge () 11 { 12 int n, m; 13 fscanf (fstd, "%s", res1); 14 fscanf (fout, "%s", res2); 15 if (strcmp (res1, res2)) return false; 16 if (!strcmp (res1, "No")) 17 { 18 fscanf (fout, "%s", res2); 19 return !strcmp (res2, "Solution!"); 20 } 21 fscanf (fin, "%d %d", &n, &m); 22 for (int i = 1; i <= n; i++) 23 fscanf (fin, "%s", name[i]); 24 int a, b; 25 for (int i = 0; i < m; i++) 26 { 27 fscanf (fin, "%s %s", s1, s2); 28 a = b = 0; 29 for (int j = 1; j <= n; j++) 30 { 31 strcmp (name[j], s1) ? 0 : a = j; 32 strcmp (name[j], s2) ? 0 : b = j; 33 } 34 cnt[a][b] = cnt[b][a] = 1; 35 } 36 int c = 0; 37 for (int i = 0; res1[i] >= '0' && res1[i] <= '9'; i++) 38 c = c * 10 + res1[i] - '0'; 39 for (int i = 0; i <= c; i++) 40 { 41 fscanf (fout, "%s", s1); 42 a = 0; 43 for (int j = 1; j <= n; j++) 44 strcmp (name[j], s1) ? 0 : a = j; 45 if (a == 0) return false; vis[a]++; 46 if (i == 0) if (a != 1) return false; 47 if (i == c) if (a != 1) return false; 48 if (i > 0) if (cnt[a][b] == 0) return false; 49 b = a; 50 } 51 if (vis[1] != 2) return false; 52 if (vis[n] != 1) return false; 53 for (int i = 2; i < n; i++) 54 if (vis[i] > 1) return false; 55 return true; 56 } 57 58 int main (int argc, char *argv[]) 59 { 60 int score = atoi (argv[1]); 61 fscore = fopen ("score.log", "w"); 62 freport = fopen ("report.log", "w"); 63 fstd = fopen (argv[2], "r"); 64 fin = fopen ("airl.in", "r"); 65 fout = fopen ("airl.out", "r"); 66 if (!fout) 67 { 68 fprintf (fscore, "%d", 0); 69 fprintf (freport, "No Output"); 70 } else if (Judge ()) 71 { 72 fprintf (fscore, "%d", score); 73 fprintf (freport, "Right"); 74 } else 75 { 76 fprintf (fscore, "%d", 0); 77 fprintf (freport, "Wrong"); 78 } 79 fclose (fscore); 80 fclose (freport); 81 return 0; 82 }