题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5067
题目意思:给出一个 n * m 的方格,每一个小方格(大小为1*1)的值要么为 0 要么为一个正整数。规定是正整数的值得方格个数不超过 10 个。现在问从最左上角的点开始,要经过所有正整数的值的点之后,要返回到最左上角的点的最短路径是多少。
其实为正整数的那些点具体值是什么根本无关紧要。可以先求出所有点到其他点的两两最短路径,然后利用状态压缩 (考虑到只有10个点,状态数最多为2^10)来求出答案),题解是这样说的:
dp[i][j]:表示状态 i 的点被访问过了,当前停留在点 j 需要的最少时间。枚举另一个不在状态 i 内的点 k , 从点 j 走到点 k 的状态转移方程为:
dp[i|(1<<k)][k] = min(dp[i|(1<<k)][k], dp[i][j] + dist(j, k))
其中,dist[j][k] 表示从点 j 与 点 k 的最短距离。所以可以利用 bfs(当然有更简单的解法,这里我是为了练手而已) 求出以每一个点作为出发点,求出以这个点到其他所有点的最短距离。
代码中的 dp 计算,和方程有一点点不同。有些地方不太理解,先留着吧。。。
1 #include <cstdio> 2 #include <cstdlib> 3 #include <cstring> 4 #include <iostream> 5 #include <algorithm> 6 #include <queue> 7 using namespace std; 8 9 const int maxn = 50 + 5; 10 const int INF = 0x3f3f3f3f; 11 int n, m, cnt, st[maxn], end[maxn]; 12 int vis[maxn][maxn], hs[maxn][maxn]; 13 int dp[maxn][1<<13], dist[maxn][maxn]; 14 15 int dx[] = {0, 1, 0, -1}; 16 int dy[] = {1, 0, -1, 0}; 17 18 struct node 19 { 20 int x, y; 21 int step; 22 } cur, tmp; 23 24 queue<node> q; 25 26 void bfs(int id) 27 { 28 while (!q.empty()) 29 q.pop(); 30 memset(vis, 0, sizeof(vis)); 31 tmp.x = st[id], tmp.y = end[id]; 32 tmp.step = dist[id][id] = 0; 33 q.push(tmp); 34 vis[st[id]][end[id]] = 1; 35 36 while (!q.empty()) 37 { 38 tmp = q.front(); 39 q.pop(); 40 41 for (int i = 0; i < 4; i++) 42 { 43 int tx = tmp.x + dx[i]; 44 int ty = tmp.y + dy[i]; 45 if (tx >= 1 && tx <= n && ty >= 1 && ty <= m && !vis[tx][ty]) 46 { 47 vis[tx][ty] = 1; 48 cur.x = tx; 49 cur.y = ty; 50 cur.step = tmp.step + 1; 51 if (hs[tx][ty] != -1) // the target point's id 52 dist[id][hs[tx][ty]] = cur.step; 53 q.push(cur); 54 } 55 } 56 } 57 } 58 59 void Init() 60 { 61 memset(hs, -1, sizeof(hs)); 62 int val; 63 cnt = 0; 64 for (int i = 1; i <= n; i++) 65 { 66 for (int j = 1; j <= m; j++) 67 { 68 scanf("%d", &val); 69 if (val || i == 1 && j == 1) 70 { 71 st[cnt] = i; 72 end[cnt] = j; 73 hs[i][j] = cnt++; // 为每一个大于0的点(最多只有10个)编号,第一个点编号为0而不是1 74 } 75 } 76 } 77 } 78 79 int main() 80 { 81 #ifndef ONLINE_JUDGE 82 freopen("in.txt", "r", stdin); 83 #endif 84 85 while (scanf("%d%d", &n, &m) != EOF) 86 { 87 Init(); 88 for (int i = 0; i < cnt; i++) // the shortest path from point i to other points 89 bfs(i); 90 int status = 1<<cnt; 91 memset(dp, 0x3f, sizeof(dp)); 92 93 dp[0][0] = 0; 94 for (int i = 0; i < status; i++) 95 { 96 for (int j = 0; j < cnt; j++) 97 { 98 if (dp[j][i] != INF) 99 { 100 for (int k = 0; k < cnt; k++) 101 { 102 if (!(i & (1<<k))) // 点k不包含在状态i上 103 dp[k][i|(1<<k)] = min(dp[k][i|(1<<k)], dp[j][i]+dist[j][k]); 104 } 105 } 106 } 107 } 108 printf("%d ", dp[0][status-1]); 109 } 110 return 0; 111 }
代码中最后的输出 dp[0][status-1] 很明显就是从最左上角的点出发,经过所有正整数点的最短距离。不过为什么求的时候与二维数组dp[][]不同,这个确实不太理解。
精简版:
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<vector> 5 using namespace std; 6 typedef long long Int; 7 typedef pair<int,int>pi; 8 int n,m; 9 vector<pi>V; 10 int Map[200][200]; 11 int dp[2058][12]; 12 int getdis(int u,int v) 13 { 14 if(!u)return V[v-1].first+V[v-1].second-2; 15 u--;v--; 16 return abs(V[v].first-V[u].first)+abs(V[v].second-V[u].second); 17 } 18 void up(int &x,int y) 19 { 20 if(x==-1||x>y)x=y; 21 } 22 int main() 23 { 24 int n,m; 25 while(scanf("%d%d",&n,&m)!=EOF) 26 { 27 V.clear(); 28 for(int i=1;i<=n;i++) 29 for(int j=1;j<=n;j++) 30 { 31 int u; 32 scanf("%d",&u); 33 if(i==1&&j==1)continue; 34 if(u>0) 35 V.push_back(pi(i,j)); 36 } 37 if(!V.size()){puts("0");continue;} 38 memset(dp,-1,sizeof(dp)); 39 dp[0][0]=0; 40 int mx=(1<<V.size())-1; 41 for(int j=0;j<mx;j++) 42 for(int k=0;k<=V.size();k++) 43 if(dp[j][k]>=0) 44 { 45 for(int p=0;p<V.size();p++) 46 if((j&(1<<p))==0) 47 { 48 up(dp[j|(1<<p)][p+1],dp[j][k]+getdis(k,p+1)); 49 } 50 } 51 int ans=-1; 52 for(int i=0;i<V.size();i++) 53 if(dp[mx][i+1]>=0) 54 up(ans,dp[mx][i+1]+getdis(0,i+1)); 55 printf("%d ",ans); 56 } 57 }