题目链接:http://poj.org/problem?id=2686
题意:百度翻译
思路:来自《挑战程序设计》 Page194
虽然可以吧城市看作顶点,道路看作边建图,但是由于车票相关的限制,无法直接使用 Dijkstra 算法求解,不过,这种情况下只需要吧状态作为定点,而把状态转移看成边来构建图就可以很好的避免这个问题。
让我们考虑一下 ”现在的城市v,此时还剩下的车票的集合为S“ 这样的状态,从这个状态出发,使用一张车票 i 属于集合 S 移动到相邻的城市u,就相当于转移到了 ”在城市 u ,此时剩下的车票的集合为 S/{i} “ 这个状态。把这个转移看成一条边,你么边上的花费就是(v-u间道路的长度)/ t ,按照上述的方法构图,就可以使用普通的 Dijkstra 来求解了
集合S使用状态压缩的方法表示就可以了。由于剩余的车票的集合 S 随着移动元素个数的不断变小,因此整个图实际上就是个DAG(有向无环图),这样我们可以通过简单的 dp 来求解了,
个人感受:书上说的很有道理,但我觉得这算法很不讲理,这道NP-hard 题可以说是我目前接触的最难理解的题目之一了,我也花了好长的时间去百度学习,才有所领悟,但是代码还是写不出来。。惭愧啊;
代码中用到了一种 集合的整数表示 的巧妙方法,主要思想是二进制和位运算,详细见我另一篇blog 集合的整数表示
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1 << 10;
const int maxm = 31;
const int INF = 1 << 29;
int n, m, p, a, b;
int t[maxm];
int d[maxm][maxm]; //图的邻接矩阵表示(-1表示没有边)
//dp[S][v] := 到达剩下的车票集合为S并且现在在城市v的状态所需要的最小花费
double dp[maxn][maxm];
void solve()
{
for (int i = 0; i < (1 << n); i++)
fill(dp[i], dp[i] + m + 1, INF); //用足够大的值初始化
dp[(1 << n) - 1][a] = 0;
double res = INF;
for (int i = (1 << n) - 1; i >= 0; i--){ // i表示当前集合 S
for (int u = 1; u <= m; u++){ //相邻城市u
for (int j = 0; j < n; j++){
if (i & (1 << j)){ //如果集合 i 中存在 j;
for (int v = 1; v <= m; v++){
if (d[v][u]){
//使用车票j,从v移动到u,
dp[i & ~(1 << j)][v] = min(dp[i & ~(1 << j)][v], dp[i][u] + (double)d[u][v] / t[j]);
}
}
}
}
}
}
for (int i = 0; i < (1 << n); i++)
res = min(res, dp[i][b]);
if (res == INF)
printf("Impossible\n");
else
printf("%.3f\n", res);
}
int main()
{
while(scanf("%d%d%d%d%d", &n, &m, &p, &a, &b)!= EOF){
if(!n && !m)
break;
memset(d, 0, sizeof(d));
for (int i = 0; i < n; i++)
scanf("%d", &t[i]);
for (int i = 0; i < p; i++){
int u, v, c;
scanf("%d%d%d", &u, &v, &c);
d[u][v] = d[v][u] = c;
}
solve();
}
return 0;
}
真难。。。