旅行商问题(TSP)之动态规划解法

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\n", ans);
62     }
63     return 0;
64 }

 

 
虽然感觉理解深了一点,但是代码的确不好写啊......

posted @ 2015-04-08 08:34  dominjune  阅读(3173)  评论(0编辑  收藏  举报