原题地址:http://acm.sgu.ru/problem.php?contest=0&problem=185
题目大意:给出一个无向图,求出从 1 到 n 的两条没有相同边的最短路径(允许有重复点),要求输出具体路径,不存在则输出"No solution"。保证两点之间没有重边。
数据范围和限制:点数 2 <= N <= 400, 边长小于等于10000。时间限制 0.25s 内存限制 4M
题目分析:
这题应该算是经典题目了,半年之前第一遍写就没过,昨天复习图论时下定决心开始搞定这道题,结果无限WA,刚刚总算把它调对了。话说SGU我还没做过几道题呢(惭愧)……
题目首先要求最短路,而且两条路必须都是最短路,而不能是一条最短的和一条次短的。那我们不妨先求出从 1 出发的单源最短路,然后考察最短路中的最优子结构性质:即若从 1 到 i 的最短路上经过了点 j ,则必须要满足 dist[j] + map[j][i] == dist[i], 否则我们总能找到一条更短的路径。这样我们不妨直接把不满足以上性质的边全部删除就好了。
下一个要求是不能有重复边。我们可以以 1 为源点, n 为汇点,剩下所有边的容量设为 1,做一遍最大流,如果最大流大于等于 2则有可行解,写一个DFS输出流的路径就行了,否则说明无重叠最短路不存在,输出无解信息
1 //date 20140115 2 #include <cstdio> 3 #include <cstring> 4 5 const int maxn = 450; 6 const int maxm = 160010; 7 const int INF = 0x7F7F7F7F; 8 9 inline int getint() 10 { 11 int ans(0); char w = getchar(); 12 while('0' > w || '9' < w)w = getchar(); 13 while('0' <= w && w <= '9') 14 { 15 ans = ans * 10 + w - '0'; 16 w = getchar(); 17 } 18 return ans; 19 } 20 21 inline int min(int a, int b){return a < b ? a : b;} 22 23 int n, m; 24 int map[maxn][maxn]; 25 26 struct edge 27 { 28 int u, v, c, next; 29 }E[maxm]; 30 int a[maxn]; 31 int nedge; 32 33 inline void add(int u, int v, int c) 34 { 35 E[++nedge].u = u; 36 E[nedge].v = v; 37 E[nedge].c = c; 38 E[nedge].next = a[u]; 39 a[u] = nedge; 40 } 41 42 int dis[maxn]; 43 inline void init() 44 { 45 static int q[maxn]; 46 static int inQ[maxn]; 47 memset(dis, 0x7F, sizeof dis); 48 memset(inQ, 0, sizeof inQ); 49 dis[1] = 0; 50 int l = 0, r = 1; q[1] = 1; inQ[1] = 1; 51 while(l < r) 52 { 53 int x = q[(++l) % maxn]; 54 for(int i = 1; i <= n; ++i) 55 if(map[x][i] > 0 && map[x][i] + dis[x] < dis[i]) 56 { 57 dis[i] = map[x][i] + dis[x]; 58 if(!inQ[i]){q[(++r) % maxn] = i; inQ[i] = 1;} 59 } 60 inQ[x] = 0; 61 } 62 63 for(int i = 1; i <= n; ++i) 64 for(int j = 1; j <= n; ++j) 65 if((map[i][j] > 0) && (dis[i] + map[i][j] > dis[j]))map[i][j] = 0; 66 67 nedge = 1; 68 for(int i = 1; i <= n; ++i) 69 for(int j = 1; j <= n; ++j) 70 if(map[i][j] > 0) 71 { 72 add(i, j, 1); 73 add(j, i, 0); 74 } 75 } 76 77 int now[maxn]; 78 int lab[maxn]; 79 80 inline int label() 81 { 82 static int q[maxn]; 83 int l = 0, r = 1; 84 memset(lab, 0xFF, sizeof lab); 85 q[1] = 1; lab[1] = 0; 86 while(l < r) 87 { 88 int x = q[++l]; 89 for(int i = a[x]; i; i = E[i].next) 90 if(E[i].c > 0 && lab[E[i].v] == -1) 91 { 92 lab[E[i].v] = lab[x] + 1; 93 q[++r] = E[i].v; 94 // now[x] = i; 95 } 96 } 97 return (lab[n] != -1); 98 } 99 100 inline int Dinic(int v, int f) 101 { 102 if(v == n)return f; 103 int w, res = 0; 104 for(int i = now[v]; i; i = now[v] = E[i].next) 105 if((E[i].c > 0) && (f > 0) && (lab[v] + 1 == lab[E[i].v]) && (w = Dinic(E[i].v, min(f, E[i].c)))) 106 { 107 E[i].c -= w; 108 E[i ^ 1].c += w; 109 f -= w; 110 res += w; 111 } 112 return res; 113 } 114 115 inline int max_flow() 116 { 117 int ans = 0; 118 for(int i = 1; i <= n; ++i)now[i] = a[i]; 119 while(label()){ans += Dinic(1, INF);for(int i = 1; i <= n; ++i)now[i] = a[i];} 120 return ans; 121 } 122 123 void dfs(int v) 124 { 125 if(v == n)printf("%d ", v); 126 for(int i = a[v]; i; i = E[i].next)if(E[i].c == 0 && map[v][E[i].v] > 0 && dis[v] + map[v][E[i].v] == dis[E[i].v]) 127 { 128 printf("%d ", v); dfs(E[i].v); map[v][E[i].v] = 0; return; 129 } 130 } 131 inline void output() 132 { 133 dfs(1), dfs(1); 134 } 135 int main() 136 { 137 freopen("sgu185.in", "r", stdin); 138 freopen("sgu185.out", "w", stdout); 139 140 n = getint(); m = getint(); 141 memset(map, 0, sizeof map); 142 memset(a, 0, sizeof a); 143 memset(E, 0, sizeof E); 144 for(int i = 1; i <= m; ++i) 145 { 146 int x, y, z; 147 x = getint(); y = getint(); z = getint(); 148 map[x][y] = map[y][x] = z; 149 } 150 init(); 151 int ans = max_flow(); 152 if(ans < 2)printf("No solution "); 153 else output(); 154 return 0; 155 }
代码说明:求最短路的时候我用的SPFA,使用邻接矩阵存储,删边之后重新建图使用邻接表,用Dinic求最大流最后DFS递归输出答案就行
一些心得和收获:昨天第一遍写的时候用了两个邻接表,但是SPFA忘记开滚动队列了,由于机房关门了回家之后也没细查。今早到学校之后重写了一遍Dijkstra版本的,但是Dijkstra好像写错了无限WA……之后该做SPFA(但还是邻接矩阵)终于查出没有滚动队列的问题,之后开始TLE on test33,才发现Dinic的当前弧优化没写好,后来改对了总算AC了,我也总算有了自己比较熟练的最大流模板了。今天一上来邻接表的范围算错了,算成了400 * 400 * 2(网络流反向边),结果被SGU内存限制卡的死死的,后来证明出最短路删边之后图就变成单向的了没必要 * 2。继续加油!