http://soj.sysu.edu.cn/show_problem.php?pid=1000&cid=1769
sicily Traveling Salesman Problem
有编号1到N的N个城市,问从1号城市出发,遍历完所有的城市并最后停留在N号城市的最短路径长度。
Input
第一行整数 T :T组数据 (T<=20)
每个case 读入一个N( 2 <= N <= 20),接着输入N行,第i行有两个整数 xi,yi表示
第i个城市坐标轴上的坐标,两个城市的距离定义为欧氏距离。
Output
每个case输出一个浮点数表示最短路径。四舍五入保留两位小数。
Sample Input
1 4 0 0 1 0 0 1 1 1
Sample Output
3.41
这题真是难,而且网上也没找到比较深入的分析......旅行商问题还没找到多项式解,如果所有城市都遍历一遍,是n!的复杂度,显然很大;还有,用贪心来解这种最短路问题是不一定能得到正确解的,因为需要每个城市都走一遍,跟dijkstra的那种最短路是不一样的。因为这题n<=20,我们可以用动态规划来做...
进入正题,首先定义状态:dp[S][j],S为集合,代表经过哪些城市,j为终点站,该状态表示经过S这些的城市到达j需要的最短路径,至于这个状态是怎么想到的,我也不知道...老师说的...我能理解已经很不错了...
---------------------------------------------------------------------------------------------
然后,先来点预备知识,怎么表示集合呢?我们用一个二进制数来表示集合,这里有四个城市(下标从0开始吧)0,1,2,3,四个城市,我们用3位二进制表示,000~111,假设现在S=5,就相当于101,就相当于我们经过了城市1,3,为什么?因为看1<<(i-1) & S是1还是0。
假如i是城市1那么1<<(1-1) & S = 1;
假如i是城市2,1<<(2-1) & S = 0;
假如i是城市3,1<<(3-1) & S = 1;
是不是刚好符合呢!通过这个判断就知道当前经过哪些城市了。
----------------------------------------------------------------------------------------------
ok,可以开始动规了!
那么,接下来又像之前那样先举些栗子:
先建图(就用样例的图吧),,图自己画一下吧,这样才容易理解,注意是双向的哦!然后,因为起点是0,所以我们不把0考虑进去咯!
还有,下面的集合S要看成是集合来理解,只是算的时候用二进制数算,理解的时候要用集合!
看状态dp[1,2][2],表示经过1,2城市到达2的最短路径,那跟什么有关呢?是不是就是经过城市1到达城市1的最短路径,加上1到2的距离?
就是dp[1][1]+dis[1][2],那么这个经过城市1的到达城市1(有点拗口)的最短路径是不是就是,从城市0出发到城市1的距离呢,dis[0][1],很明显,dp数组的初始值,就是dp[0][i]=dis[0][i]了,所以这里我们可以初始化dp[1][i]了,而且这个转移方程也有一点苗头了:
dp[S][j] = min(dp[S-{j}][i]+dis[i][j], dp[S][j]),这里的i指的是在那些经过的城市里面选的。
好像栗子不太够,再来一个:
dp[1,2,3][3],取决于前一个经过城市2又或者是1->dp[1,2][2]+dis[2][3] or dp[1,2][1]+dis[1][3],再继续,看前一种:
经过1,2到达2,好理解,就是经过1到达1再加上1,2的距离,跟上一个栗子一样;后面一种,经过1,2到达1,就是经过2到达1再加1,2的距离,而经过2到达1,就是0,2的距离再加上2,1的距离,这种还说不定会快一下,谁知道呢,所以就要不断更新!
还是感觉下标好乱......
最后看一下下标从1开始的代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 6 using namespace std; 7 8 double const INF = 1e8; 9 10 struct point { 11 int x; 12 int y; 13 }p[25]; 14 15 double d(point a, point b) 16 { 17 return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y)); 18 } 19 20 double dis[25][25], dp[1111110][25]; 21 22 int main() 23 { 24 int t; 25 scanf("%d", &t); 26 while(t--) 27 { 28 int n; 29 scanf("%d", &n); 30 int count=1; 31 double ans = INF; 32 33 for(int i=0; i<n-1; i++) //集合 10000 34 count <<= 1; 35 36 for(int i=1; i<=n; i++) 37 scanf("%d%d", &p[i].x, &p[i].y); 38 39 for(int i=1; i<count; i++) //注意初始化的下标 40 for(int j=1; j<=n; j++) 41 dp[i][j] = INF; 42 43 for(int i=1; i<=n; i++) 44 for(int j=1; j<=n; j++) 45 dis[i][j] = d(p[i], p[j]); 46 47 for(int i=1; i<=n; i++) 48 dp[1][i] = dis[1][i]; 49 50 for(int s=2; s<count; s++) //集合 51 for(int j=2; j<=n; j++) 52 { 53 for(int i=2; i<=n; i++) 54 { 55 int temp = 1 << (i-1); 56 if(temp & s) 57 dp[s][j] = min(dp[s-temp][i]+dis[i][j], dp[s][j]); 58 } 59 } 60 ans = dp[count-1][n]; 61 printf("%.2lf ", ans); 62 } 63 return 0; 64 }
虽然感觉理解深了一点,但是代码的确不好写啊......