一、题目大意
给我们一个无向图,起点和终点,要求我们计算出从起点到达终点的路径。
题目限制我们最多只能走 N 步,每次走一步消耗一张票,但可以享受到这张票带来的加速,例如第1步是从1走到3且使用第2张票,则消耗的时间为 d[1][3]/T[2]。
(1<=N<=8,每条边长度不超过100)
二、解题思路
我们定义DP数组,dp[S][v]代表我们使用的的票的集合为S且到达v的最短时间。起初的时候给整个数组填满无穷大(1e7就够了),对起点A做初始化 dp[0][A]=0,然后让S从空集开始主键向全集循环,循环内部再循环所有的票i、路径端点v和u。
如果S包含第 i 张票,则更新dp[S][v] = min(dp[S][v] , dp[ S去掉 i 的差集 ][u] + d[u][v] / T[i] )
min里的第二个参数,代表我们使用一些票,走到第 u 的位置,之后再使用第 i 张票从 u 走到 v 的距离。
最终循环结束后,找出各种用票方式,到达终点的最小值mint。
如果 mint > (票数 * 100)代表走不到终点,输出“不可能”。
否则输出 mint即可(%.3f)
三、代码
#include <iostream>
using namespace std;
const int MAX_N = 8;
const int MAX_M = 30;
const double INF = 10000000.0;
const int INF_INT = 10000000;
double dp[1 << MAX_N][MAX_M];
int T[MAX_N];
int d[MAX_M][MAX_M];
int N, M, P, A, B;
void solve()
{
fill(dp[0], dp[0] + (1 << MAX_N) * MAX_M, INF);
dp[0][A] = 0.0;
for (int S = 0; S < (1 << N); S++)
{
for (int i = 0; i < N; i++)
{
for (int v = 0; v < M; v++)
{
for (int u = 0; u < M; u++)
{
if (S >> i & 1)
{
dp[S][v] = min(dp[S][v], dp[S & ~(1 << i)][u] + (d[u][v] * 1.0) / T[i]);
}
}
}
}
}
}
void putAns()
{
double mint = INF;
for (int S = 0; S < (1 << N); S++)
{
mint = min(mint, dp[S][B]);
}
if (mint > 1000.0)
{
printf("Impossible\n");
}
else
{
printf("%.3f\n", mint);
}
}
int main()
{
int x, y, z;
while (true)
{
scanf("%d%d%d%d%d", &N, &M, &P, &A, &B);
if (N == 0 && M == 0 && P == 0 && A == 0 && B == 0)
{
break;
}
--A, --B;
for (int i = 0; i < N; i++)
{
scanf("%d", &T[i]);
}
fill(d[0], d[0] + (MAX_M * MAX_M), INF_INT);
for (int i = 0; i < M; i++)
{
d[i][i] = 0;
}
for (int i = 0; i < P; i++)
{
scanf("%d%d%d", &x, &y, &z);
d[--x][--y] = z;
d[y][x] = z;
}
solve();
putAns();
}
return 0;
}
四、相关参考文献
《挑战程序设计竞赛(第二版)》P191 - P195