题目链接
Description:
(多实例测试)
有一个旅行家计划乘马车旅行。他所在的国家共有m个城市,在城市之间有p条道路相连接。从某个城市沿着某条道路到相邻的城市需要乘坐马车。而乘坐马车需要使用车票,每用一张车票只可以通过一条道路。每张车票上都记有马的匹数,从一个城市移动到另一个城市的所需时间等于城市之间道路的长度除以马的数量的结果。这位旅行家一共有n张车票,第i张车票上的马的匹数是ti.一张车票只能用一次,并且换乘所需时间可以忽略。求从城市a到城市b所需要的最短时间。如果无法到达城市b输出"Impossible"。
Input
第一行包含五个整数n,m,p,a,b,对应题目中的描述
第二行包含 n 个数,第 i 个数表示第 i 张票上的马匹数
接下来 p 行,每行三个数,A,B,C,表示从A城市到B城市有一条长度位C的路
Output
每个输出占一行,输出从城市a到城市b所需要的最小时间,如果无法到达,输出"Impossible"。
Sample Input
3 4 3 1 4
3 1 2
1 2 10
2 3 30
3 4 20
2 4 4 2 1
3 1
2 3 3
1 3 3
4 1 2
4 2 5
2 4 3 4 1
5 5
1 2 10
2 3 10
3 4 10
1 2 0 1 2
1
8 5 10 1 5
2 7 1 8 4 5 6 3
1 2 5
2 3 4
3 4 7
4 5 3
1 3 25
2 4 23
3 5 22
1 4 45
2 5 51
1 5 99
0 0 0 0 0
Sample Output
30.000
3.667
Impossible
Impossible
2.856
说明
本题没有精度要求,对于上述样例,输出以下结果也是可以的
30.0
3.66667
Impossible
Impossible
2.85595
题解:
首先我们可以想到用dfs遍历所有情况来解决这个问题,但是dfs复杂度太高,不适用这个题目,可以将dfs改为记忆化搜索的方法,但是记忆化搜索也无法满足本题目,因为调用函数占用的时间也很多,最后将递归改为递推式,利用动态规划的思想,将记忆化搜索改为递推式动态规划写法即可。
先说说如何写dfs,首先需要一个集合去判断去判断n张票哪一张被使用过了,进行标记,再者需要一个遍历now,表示当前抵达的城市,只需要每次去遍历所有的票,从没选取的中选择一个,施加标记,然后计算出一个结果,继续dfs,直到遇到点b,说明抵达目的地,或者是集合中的票被使用完了,说明无法到达,用一个全局遍历res在每次抵达目的地后更新res,如果res没被更新过说明无法到达,否则就输出res即可。
dfs复杂度高的原因是存在很多重复计算,只要将重复的计算删除,就可以实现高效的记忆化搜索。显然本题的搜索状态有两个,分别是票的集合以及当前到达的点,集合如果用一个数组实现的话无法将其放入到dp数组中,所以可以用一个整数来表示这个集合,如果整数i的第k位是0,表示第k张票没被选取,否则表示被选取整数的大小依赖于票数,如果有n张票,整数至少要(1<<n)这么大。因此我们可以定义二维数组来记录每一个dfs两个确定参数的状态,每次搜索到之后将dp数组对应值更新,如果下次进入dfs的状态在dp数组中被更新过,就直接返回值,否则就进行计算并在计算完成后更新dp数组中对应状态的值。
double dfs(int index, int now) {//index表示选票的集合 now表示当前所在的城市
if (dp[index][now] < inf) {
return dp[index][now];
}
if (now == b) {
return dp[index][now] = 0;
}
if (index == (1 << n) - 1) {
return inf * 1.0;
}
for (re int i = 1; i <= m; i++) {//遍历每一个即将要去的城市
if (g[now][i] >= 0) {
for (re int j = 0; j < n; j++) {//遍历要选择的票
if (!(index >> j & 1)) {//选择票必须选择之前没有选择过的
dp[index][now] = min(dp[index][now], dfs(1 << j | index, i) + (double)g[now][i] / ticket[j]);
// dp[index][now]=min(dp[index][now],dp[1<<j|index],i)+(double)g[now][i]/ticket[j];//状态转移方程
}
}
}
}
}
通过记忆化搜索我们可以得到动态规划的状态转移方程,通过状态转移方程可以得知,计算过程中的状态是从集合大的方向往集合小的方向转移,因此可以得知递推式的循环顺序,从大集合向小集合循环,知道了循环方向,初始化一定是初始化循环的起点,也就是不需要计算可以得知的状态,由此我们可以得到循环递推的代码
for (re int i = (1 << n) - 1; i >= 0; i--) {
for (re int j = 1; j <= m; j++) {//遍历当前所在的城市
for (re int k = 0; k < n; k++) {//遍历要选择的票
if (!(i >> k & 1))for (re int l = 1; l <= m; l++) {//遍历要去的城市
// 要求:票不能被选过一次,即不能在集合 i 中
if (g[j][l] >= 0) {//城市 j 和 l 之间必须有路相连
dp[i][j] = min(dp[i][j], dp[1 << k | i][l] + (double)g[j][l] / ticket[k]);//转移方程
}
}
}
}
}
完整代码:
#include<iostream>
#include<cstring>
using namespace std;
#pragma G++ optimize(2)
int n, m, p, a, b;
int ticket[10];
int g[33][33];
#define re register
const int maxn = 1 << 12;
const int inf = 0x3f3f3f3f;
double dp[maxn][33];
double ans;
double temp;
double dfs(int index, int now) {//记忆化搜索
//index表示选票的集合 now表示当前所在的城市
if (dp[index][now] < inf) {
return dp[index][now];
}
if (now == b) {
return dp[index][now] = 0;
}
if (index == (1 << n) - 1) {
return inf * 1.0;
}
for (re int i = 1; i <= m; i++) {//遍历每一个即将要去的城市
if (g[now][i] >= 0) {
for (re int j = 0; j < n; j++) {//遍历要选择的票
if (!(index >> j & 1)) {//选择票必须选择之前没有选择过的
dp[index][now] = min(dp[index][now], dfs(1 << j | index, i) + (double)g[now][i] / ticket[j]);
// dp[index][now]=min(dp[index][now],dp[1<<j|index],i)+(double)g[now][i]/ticket[j];
}
}
}
}
}
int main()
{
while (~scanf("%d %d %d %d %d", &n, &m, &p, &a, &b) && (n || m || p || a || b)) {
for (re int i = 0; i < n; i++)scanf("%d", ticket + i);
int t1, t2, t3;
memset(g, -1, sizeof g);
for (re int i = 0; i < maxn; i++)for (re int j = 0; j < 33; j++)dp[i][j] = inf;
for (re int i = 0; i < p; i++) {
scanf("%d %d %d", &t1, &t2, &t3);
g[t1][t2] = g[t2][t1] = t3;
}
dp[(1 << n) - 1][a] = 0;//dp数组初始化
for (re int i = (1 << n) - 1; i >= 0; i--) {//递推式
for (re int j = 1; j <= m; j++) {//遍历当前所在的城市
for (re int k = 0; k < n; k++) {//遍历要选择的票
if (!(i >> k & 1))for (re int l = 1; l <= m; l++) {//遍历要去的城市
// 要求:票不能被选过一次,即不能在集合 i 中
if (g[j][l] >= 0) {//城市 j 和 l 之间必须有路相连
dp[i][j] = min(dp[i][j], dp[1 << k | i][l] + (double)g[j][l] / ticket[k]);//转移方程
}
}
}
}
}
double res = inf * 1.0;
for (re int i = 0; i < (1 << n); i++) {//从所有到达 b 的状态中选取最小值即是答案
res = min(res, dp[i][b]);
}
res == inf ? puts("Impossible") : printf("%lf\n", res);
}
return 0;
}