07_旅行商问题(TSP问题,货郎担问题,经典NPC难题)

问题来源:刘汝佳《算法竞赛入门经典--训练指南》 P61 问题9:

问题描述:有n(n<=15)个城市,两两之间均有道路直接相连,给出每两个城市i和j之间的道路长度L[i][j],求一条经过每个城市一次且仅一次,最后回到起点的路线,使得经过的道路总长度最短(城市编号为0~n-1)。

分析: 1.因为最后走的路线为一个环,可以设城市0为起点城市。

    2.将每个城市看作二进制的一个位(1代表有,0代表没有),则数k可以表示一些城市的集合(例如k=13,二进制表示为1101,表示城市0,2,3的集合),我们可以求得k<=2^15-1,令  aim=2^15-1;

    3.dp[k][j]表示经过了k集合中的所有城市并且以j城市为终点的路径的最小值

    则dp[k][j] = Min{dp[k][j],dp[k-j][i]+dis[i][j] | (0<=i<=n-1 && i属于集合k)};(其中k-j表示集合k中去掉数j后的集合(所以j应该是集合k中的元素)),

例题链接:http://acm.fzu.edu.cn/problem.php?pid=2186

例题: fzu 2186

Problem 2186 小明的迷宫

Accept: 88    Submit: 270
Time Limit: 1000 mSec    Memory Limit : 32768 KB

 Problem Description

小明误入迷宫,塞翁失马焉知非福,原来在迷宫中还藏着一些财宝,小明想获得所有的财宝并离开迷宫。因为小明还是学生,还有家庭作业要做,所以他想尽快获得所有财宝并离开迷宫。

 Input

有多组测试数据。

每组数据第一行给出两个正整数n,m(0<n,m<=100)。代表迷宫的长和宽。

接着n行,每行m个整数。正数代表财宝(财宝的个数不超过10);负数代表墙,无法通过;0代表通道。

每次移动到相邻的格子,所花费的时间是1秒。小明只能按上、下、左、右四个方向移动。

小明的初始位置是(1,1)。迷宫的出口也在(1,1)。

 Output

输出获得所有财宝并逃出迷宫所花费的最小时间,如果无法完成目标则输出-1。

 Sample Input

3 3
0 0 0
0 100 0
0 0 0
2 2
1 1
1 1

 Sample Output

4
4

 Source

FOJ有奖月赛-2015年03月
思路:先将出口和宝藏之间的距离全部通过 DFS求出来(出口的地方需要特殊判断和处理),接下来就是裸的 TSP问题dp+状压
  1 #include "stdio.h"

  2 #include "string.h"

  3 #include "queue"

  4 using namespace std;

  5 #define N 105

  6 #define INF 0x3fffffff

  7 

  8 int m,n;

  9 int map[N][N],mark[N][N];

 10 int dis[15][15],flag[15],dist[15];

 11 int dir[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};

 12 

 13 int dp[5050][15],b[15];

 14 int inline Min(int a,int b){ return a<b?a:b; }

 15 

 16 struct Point

 17 {

 18     int x,y;

 19 } point[15];

 20 

 21 struct node

 22 {

 23     int x,y;

 24     int length;

 25 };

 26 

 27 void DFS(int start,int x,int y,int length)

 28 {

 29     int i,k;

 30     int x1,y1;

 31     queue<node> q;

 32     node cur,next;

 33     cur.x = x;

 34     cur.y = y;

 35     cur.length = 0;

 36     q.push(cur);

 37     while(!q.empty())

 38     {

 39         cur = q.front();

 40         q.pop();

 41         for(i=0; i<4; i++)

 42         {

 43             next.x = x1 = cur.x+dir[i][0];

 44             next.y = y1 = cur.y+dir[i][1];

 45             next.length = cur.length+1;

 46             if(x1<=0 || x1>n || y1<=0 || y1>m || mark[x1][y1]!=0 || map[x1][y1]<0)

 47                 continue;

 48             if(map[x1][y1]>0)

 49             {

 50                 k = map[x1][y1];

 51                 dis[start][k] = dis[k][start] = next.length;

 52             }

 53             mark[x1][y1] = 1;

 54             q.push(next);

 55         }

 56     }

 57 }

 58 

 59 int main()

 60 {

 61     int ans;

 62     int i,j,k,num,l;

 63     b[0] = 1;

 64     for(i=1; i<15; i++)  //b[i]存的2^i

 65         b[i] = b[i-1]*2;

 66     while(scanf("%d %d",&n,&m)!=EOF)

 67     {

 68         num = 2;

 69         for(i=1; i<=n; ++i)

 70         {

 71             for(j=1; j<=m; j++)

 72             {

 73                 scanf("%d",&map[i][j]);

 74                 if(i==1 && j==1&& map[i][j]>0) num--;

 75                 if(map[i][j]>0)      map[i][j]= num++;

 76                 else if(map[i][j]<0) map[i][j]=-1;

 77             }

 78         }

 79         if(map[1][1]<0)

 80         {

 81             printf("-1\n");

 82             continue;

 83         }

 84         map[1][1] = 1;

 85         for(i=1; i<=n; ++i)

 86         {

 87             for(j=1; j<=m; j++)

 88             {

 89                 if(map[i][j]>0)

 90                 {

 91                     k = map[i][j];

 92                     point[k].x = i, point[k].y = j;

 93                 }

 94 

 95             }

 96         }

 97         for(i=1; i<num; i++)

 98         {

 99             for(j=1; j<num; j++)

100                 dis[i][j] = INF;

101             dis[i][i]= 0;

102         }

103         for(i=1; i<num; i++)  //以每个点为起点广搜,求出任意两点间的最短距离

104         {

105             memset(mark,0,sizeof(mark));

106             mark[point[i].x][point[i].y] = 1;

107             DFS(i,point[i].x,point[i].y,0);

108         }

109         num--;  //宝藏编号为1~num

110         int tt = 0;

111         for(i=2; i<=num; i++)

112         {

113             if(dis[1][i]==INF)  //只要存在一个INF,表示至少有一个宝藏不可达,输出-1

114                 tt = 1;

115         }

116         if(tt==1)

117         {

118             printf("-1\n");

119             continue;

120         }

121         int aim =b[num+1]-2;//y因为从2^1开始到2^num,则aim = 2^(num+1)-1-2^0

122         for(i=0; i<=aim; i++)

123         {

124             for(j=0; j<=num; j++)

125                 dp[i][j] = INF;

126         }

127         dp[2][1] = 0; 

128         for(l=2; l<=aim; l++) //一个集合l

129         {

130             for(i=1; i<=num; i++)

131             {

132                 for(j=1; j<=num; j++)

133                 {

134                     if(i==j) continue;

135                     if((b[i]&l)==0) continue;  //必须满足i  在集合l中,不满足,跳过,

136                     if((b[j]&l)==1) continue;  //必须满足j不在集合l中,不满足,跳过,

137                     if(dp[l][i]==INF) continue;

138                      dp[l|b[j]][j]=Min(dp[l|b[j]][j],dp[l][i]+dis[i][j]);

139                 }

140             }

141         }

142         ans = INF;

143         for(i=2; i<=num; i++)

144             ans = Min(ans,dp[aim][i]+dis[i][1]);

145         printf("%d\n",ans);

146     }

147     return 0;

148 }

 

你可能感兴趣的:(问题)