状态压缩DP(2)--Poj2686 TRSTAGE - Traveling by Stagecoach

原题链接

题目大意

有一个旅行家计划乘马车旅行。他所在的国家里共有 m m m 个城市,在城市之间有若干道路相连。从某个城市沿着某条道路到相邻的城市需要乘坐马车。而乘坐马车需要使用车票,每用一张车票只可以通过一条道路。每张车票上都记有马的匹数,从一个城市移动到另一个城市
的所需时间等于城市之间道路的长度除以马的数量的结果。这位旅行家一共有 n n n张车票,第 i i i张车票上的马的匹数是 t i t_i ti, 一张车票只能使用一次,并且换乘所需要的时间可以忽略。求从城市 a a a 到城市 b b b所需要的最短时间。如果无法到达城市 b b b 则输出"Impossible"。

思路:状态压缩+DAG的最短路

所谓状态压缩即简介,之前我写过旅行商问题的状态压缩DP的解法,该问题与旅行商问题大相径庭。不过应该压缩何种状态,DP的状态转移方程式都是值得思考的问题。

拿到题看得出来是一道最短路的问题 我看到图就感觉是最短路的问题,但是车票是一个限制因素,单纯的迪杰斯特拉是不能解决的。很可能真实的最短路要通过的路径数目比车票数目还多,而且不知道哪里应该使用哪张车票,我有一个大胆的想法,以车票作为约束,我们分别寻找只使用一张车票,使用两张车票,。。使用 n n n张车票分别的最短路径,然后越长的路给越多马匹的车票,最后在这 n n n种情况取用时最少的。这样的思路感觉已经比较接近答案了 ,看了一眼题解,还是状态压缩比较短小精悍

我们用整数表示目前剩余的车票集合, d p [ S ] [ u ] dp[S][u] dp[S][u]表示剩余车票集合为 S S S的情况下,到达节点 u u u所需要的时间。那么状态转移的话,就是使用 S S S中的一张车票 i i i,到达 u u u的临近点 v v v,该状态转移所需要的花费是 d [ u ] [ v ] / t [ i ] d[u][v]/t[i] d[u][v]/t[i],这样讲车票集合从多到少进行枚举,不断进行状态转移,就可以求出最终结果,具体步骤见代码注释

这样做的结果是将所有的城市/车票组合都构建成了一张图从起点开始不断向外扩张,比如 t = { 1 , 3 } t=\{1,3\} t={1,3},道路网如下,从2到1
在这里插入图片描述在这里插入图片描述

AC代码

#include<iostream>
#include<iomanip>
#include<string.h>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
using namespace std;

#define ll int
#define MaxN 10
#define MaxM 35
#define inf 222222222

ll n, m, p, a, b, t[MaxN], d[MaxM][MaxM];
double dp[1 << MaxN][MaxM], res = inf;//dp[S][i]:剩余船票集合为S的情况下,走到i点需要的时间
int main() {
	while (cin >> n >> m >> p >> a >> b)
	{
		if (n == 0 && m == 0 && p == 0 && a == 0 && b == 0)return 0;
		res = inf; memset(d, -1, sizeof(d));//初始化
		for (ll i = 0; i < n; i++)cin >> t[i];
		for (ll i = 1; i <= p; i++) {
			ll a, b, c; cin >> a >> b >> c;
			d[a][b] = c; d[b][a] = c;
		}
		for (ll s = 0; s < 1 << n; s++)for (ll k = 1; k <= m; k++)dp[s][k] = inf;
		dp[(1 << n) - 1][a] = 0;//不用船票,到a用时为0.
		//从多到少依次枚举集合
		for (ll s = (1 << n) - 1; s >= 0; s--) {
			res = min(res, dp[s][b]);
			for (ll v = 1; v <= m; v++) {//每个点
				for (ll tmp = 0; tmp <= n; tmp++) {//每个车票
					if (s >> tmp & 1) {//该车票属于集合S,也就是车票尚未使用
						for (ll u = 1; u <= m; u++) {//每个车票都有机会用来更新他的临近点
							if (d[v][u] >= 0) {//v可以到u
								dp[s&~(1 << tmp)][u] = min(dp[s&~(1 << tmp)][u], dp[s][v] + 1.0*d[v][u] / t[tmp]);
							}
						}
					}
				}
			}
		}
		if (res == inf) cout << "Impossible" << endl;
		else cout << res << endl;
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值