动态规划——城市里的间谍

*描述及讲解摘自其它博客,代码自打

Description

Sample Input


55 
5 10 15 

0 5 10 20 

0 5 10 15 

18 
1 2 3 

0 3 6 10 12 

0 3 5 7 12 15 

30 
20 

20 

1 3 5 7 11 13 17 
0

Sample Output

Case Number 1: 5 
Case Number 2: 0 
Case Number 3: impossible

题目大意

某城市的地铁是线性的,有n(2≤n≤50)个车站,从左到右编号为1~n。有M1辆列车从第1站开始往右开,还有M2辆列车从第n站开始往左开。在时刻0,Mario从第1站出发,目的是在时刻T(0≤T≤200)会见车站n的一个间谍。在车站等车时容易被抓,所以她决定尽量躲在开动的火车上,让在车站等待的总时间尽量短。列车靠站停车时间忽略不计,且Mario身手敏捷,即使两辆方向不同的列车在同一时间靠站,Mario也能完成换乘。 
输入第1行为n,第2行为T,第3行有n-1个整数t1,t2,…,tn−1t1,t2,…,tn−1(1≤ti≤701≤ti≤70),其中titi表示地铁从车站i到i+1的行驶时间(两个方向一样)。第4行为M1(1≤M1≤50),即从第1站出发向右开的列车数目。第5行包含M1个整数d1, d2,…, dM1(0≤di≤250,di<di+1),即各列车的出发时间。第6、7行描述从第n站出发向左开的列车,格式同第4、5行。输出仅包含一行,即最少等待时间。无解输出impossible。

分析

时间是单向流逝的,是一个天然的“序”。影响到决策的只有当前时间和所处的车站,所以可以用d(i,j)表示时刻i,你在车站j(编号为1~n),最少还需要等待多长时间。边界条件是d(T,n)=0,其他d(T,i)(i不等于n)为正无穷。有如下3种决策。

  • 决策1:等1分钟。
  • 决策2:搭乘往右开的车(如果有)。
  • 决策3:搭乘往左开的车(如果有)。

在程序中定义一个数组has_trainhas_train[t][i][0]表示时刻t,在车站i是否有往右开的火车,has_train[t][i][1]类似,不过记录的是往左开的火车。

状态有O(nT)O(nT)个,每个状态最多只有3个决策,因此总时间复杂度为O(nT)O(nT)。

——摘自《算法竞赛入门经典(第2版)》

题的核心是逆推的思想

这道题一看要问最少需要等待多少时间,自然会想到用dp,那么怎么来处理这个问题呢?我们可以用dp[i][j]来表示时刻i,你现在身处第j个站,最少还需要等待多长时间,我们所知的是,在T时刻,你人一定需要在第n个车站去完成间谍任务,所以dp[T][n]=0;可以由这个位置来进行反推,比如我在时刻T-1可以仍然位于车站n或者说如果有往左开的车我就有选择往左坐车的可能; 
那么我们对于一个dp[i][j]进行状态转移,有三种转移方式:

决策一:

是我呆在原地不动,这个时候需要等的时间是由dp[i+1][j]转移而来,需要在dp[i+1][j]的基础上+1;因为我在j+1的时候的最优等待值的基础上,等待一个时间(时间从后往前逆推,注意dp数组表示的是最少还需要等待多少时间)

dp[i][j]=dp[i+1][j]+1;
  • 1

决策二:

往右移动,如果往右有车的话 
那么dp[i][j]就需要从dp[i+t[j]][j+1]转移过来 
dp[i+t[j]][j+1]表示的是时刻i+t[j]的时候(相当于是往右到下一站j+1站的时候的最优值),由于是逆推的,所以dp[i+t[j]][j+1]的值先于dp[i][j]计算出,所以dp转移合理

dp[i][j]=min(dp[i][j],dp[i+t[j]][j+1]);
  • 1

决策三:

往左移动,如果往左有车的话 
那么dp[i][j]就需要从dp[i+t[j-1]][j-1]转移过来,原理和上面一样,往左需要花t[j-1]的时间,i+t[j-1]的时刻,位置在左一站的j-1站时的最优值,由于循环里先循环的是i,而且是逆向循环,所以dp[i+t[j-1]][j-1]的值也先已经计算得出,所以dp转移合理 

dp[i][j]=min(dp[i][j],dp[i+t[j-1]][j-1]);

代码: 

#define INF 100

#include
#include
#include
using namespace std;

int main()
{
	int has_train[INF][INF][2] ;
	int n, T,M1,M2,i,j,a;
	int t[INF] = { 0 }, dp[INF][INF]; //dp[i][j]表示i时刻,在车站j,最少还要多少等待时间
	memset(dp, 0, sizeof(dp));  
	memset(has_train, 0, sizeof(has_train));
	cin >> n >> T;
	for (i = 1; i < n; i++) {   //t[i]只需要n-1个
		cin >> t[i];
	}
	cin >> M1;
	for (i = 1; i <= M1; i++) {
		cin >> a;  //a时出发
		for (j = 1; j <= n; j++) {
			has_train[a][j][0] = 1;  //在a时刻,j站有向右的车
			a += t[j];    //加上到下一个站的时间
		}
	}
	cin >> M2;
	for (i = 1; i <= M2; i++) {
		cin >> a;
		for (j = n; j >=1; j--) {
			has_train[a][j][1] = 1;
			a += t[j-1];   //是向左出发
		}
	}
	/*  用递推    */
	for (i = 1; i <= n - 1; i++) {
		dp[T][i] = INF;
	}
	dp[T][n] = 0;  //我们想要做到T时刻n站时不用等车//用倒推,在T时刻n站的最小等待时间为0
	for (i = T - 1; i >= 0; i--) {
		for (j = 1; j <= n; j++) {
			dp[i][j] = dp[i + 1][j] + 1;  //原地不动,等待一分钟
			if (j < n && has_train[i][j][0] && i + t[j] <= T) {

				dp[i][j] = min(dp[i][j], dp[i + t[j]][j + 1]);  //dp[i+t[j]][j+1]表示的是时刻i+t[j]的时候(相当于是往右到下一站j+1站的时候的最优值)
				                            				   //由于是逆推的,所以dp[i+t[j]][j+1]的值先于dp[i][j]计算出,所以dp转移合理
			}
			if (j > 1 && has_train[i][j][1] && i + t[j - 1] <= T) { //t[j-1]表示从上一站到这站的时间

				dp[i][j] = min(dp[i][j], dp[i + t[j - 1]][j - 1]);  //如果向左上车,它的等待时间就i + t[j-1]时刻在j-1站所需的等待时间
																  //因为在这站没有花费等待时间
			}
			//cout << dp[i][j]<<" ";
		}
	}
	if (dp[0][1] < INF) {  //最少等待时间会保存在dp[0][1]
		cout << "所需的最少等待时间为:" << dp[0][1];
	}
	else {
		cout << "impossible";
	}
	return 0;
}

结果:动态规划——城市里的间谍_第1张图片 

你可能感兴趣的:(动态规划——城市里的间谍)