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
");
 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
");
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
",ans);
146     }
147     return 0;
148 }
原文地址:https://www.cnblogs.com/ruo-yu/p/4385291.html