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
Sample Output
Source
FOJ有奖月赛-2015年03月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 }