A Spy in the Metro, ACM/ICPC World Finals 2003, UVa1025

题目描述

有n个车站,编号1到n。从1号车站开往n号车站的列车有M1趟,发车时间为d1,d2,...,d_{M1}。从n号车站开往1号车站的列车有M2趟,发车时间类似。列车驶过每两个车站间的用时为t_{1},t_{2},...,t_{n-1}。现在有一个间谍,0时刻在车站1,他要在T时刻到达车站n。求间谍在车站逗留的最短时间。

题解

一道动态规划的好题。自己初学动态规划,结合该题记录一下自己的感悟。

动态规划中有几个概念:

  • 阶段:把所给求解问题的过程恰当地分成若干个相互联系的阶段。阶段间的联系一般不是简单的线性关系,并且解决问题的阶段可能在代码中体现的十分抽象。
  • 状态:状态表示每个阶段开始面临的自然状况或客观条件,它是某个阶段的出发位置。明确状态是解决动态规划问题的一个重点。
  • 决策:一个阶段的状态给定以后,从该状态演变到下一阶段某个状态的一种选择(行动)称为决策。通常,决策有很强的实际意义。在明确了状态后,分析出问题的决策是写出状态转移方程的关键。
  • 最优化原理(最优子结构性质):一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。最优化原理是确定决策的理论和要求,它是整个过程的最优策略(注意与贪心策略区分)。通常为选取几个策略中的最大值或最小值。同时,从子问题子结构的角度考虑,可以帮助我们找出问题的状态。 
  • 无后效性:将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。无后效性是贪心问题与动态规划的本质区别。

回到本题。

通过简单模拟该题的过程可以发现几个很重要的状态:在某个时间、在某个车站、是否在车上。但考虑到最终只是要求在时间T到达车站n,且上下车的时间不计。所以是否在车上这个状态可以忽略。

那么本题的状态只有两个:时间 i 与车站 j ,则设dp(i, j)(0\leqslant i \leq T,1\leqslant j\leqslant n)表示在时间 i 和 车站 j 时还需等待的最短时间。

考虑这两状态,那么决策有三个:

决策1:等待一分钟, dp(i, j) = dp(i+1, j) + 1

决策2:乘坐向左开的车(如果有), dp(i, j) = dp( i + t[j-1], j - 1)

决策3:乘坐向右开的车(如果有), dp(i, j) = dp( i + t[j], j + 1)

那么状态转移方程就是在这三个决策中选择一个最小的。

有了状态和状态转移方程后便可代码实现。代码实现有两种方案可选,具体如下:

1. 递推

如果使用递推,我们就必须自己划分阶段,确定计算顺序。观察决策可以发现,状态(i,j)都是由状态(l,k)转移而来,其中 i < l。这就要求必自底向上计算。核心代码如下:

    memset(dp, 0, sizeof(dp));
    for(int i = 1; i < n; i++) dp[T][i] = INF;
    dp[T][n] = 0;

    for(int i = T-1; i >=0; i--)
    for(int 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]);
        if(j>1 && has_train[i][j][1] && i+t[j-1] <= T)
            dp[i][j] = min(dp[i][j], dp[i+t[j-1]][j-1]);
    }

2.记忆化搜索

记忆化搜索隐含了计算顺序。核心代码如下:

int solve(int i, int j){
    if(vis[i][j]) return dp[i][j];
    vis[i][j] = true;
    if(i==T){
        if(j == n) return dp[i][j] = 0;
        else return dp[i][j] = INF;
    }
    int a,b = INF, c = INF;
    a = solve(i+1, j)+1;
    if(j<n && has_train[i][j][0] && i+t[j] <= T)
        b = solve(i+t[j], j+1);
    if(j>1 && has_train[i][j][1] && i+t[j-1] <= T)
        c = solve(i+t[j-1], j-1);
    return dp[i][j] = min(a,min(b,c));
}

AC代码

1.递推

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 300;
const int INF = 0x3f3f3f3f;

int t[maxn],d[maxn], e[maxn];
int dp[maxn][maxn];
bool has_train[maxn][maxn][2];

int main(){
    int n,T,m1,m2, kase = 0;
    while(scanf("%d", &n) != EOF && n){
    memset(has_train, false, sizeof(has_train));
    scanf("%d", &T);
    for(int i = 1; i < n; i++){
        scanf("%d", t+i);
    }
    scanf("%d", &m1);
    for(int i = 0; i < m1; i++){
        scanf("%d", d+i);
        for(int j = 1,tt = d[i]; j <= n; j++){
            has_train[tt][j][0] = true;
            tt += t[j];
        }
    }
    scanf("%d", &m2);
    for(int i = 0; i < m2; i++){
        scanf("%d", e+i);
        for(int j = n, tt = e[i]; j>0; j--){
            has_train[tt][j][1] = true;
            tt += t[j-1];
        }
    }

    memset(dp, 0, sizeof(dp));
    for(int i = 1; i < n; i++) dp[T][i] = INF;
    dp[T][n] = 0;

    for(int i = T-1; i >=0; i--)
    for(int 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]);
        if(j>1 && has_train[i][j][1] && i+t[j-1] <= T)
            dp[i][j] = min(dp[i][j], dp[i+t[j-1]][j-1]);
    }
    printf("Case Number %d: ", ++kase);
    if(dp[0][1] >= INF) printf("impossible\n");
    else printf("%d\n", dp[0][1]);
    }

}

 2.记忆化搜索

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 300;
const int INF = 0x3f3f3f3f;

int t[maxn],d[maxn], e[maxn];
int dp[maxn][maxn];
bool has_train[maxn][maxn][2];
bool vis[maxn][maxn];

int n,T,m1,m2, kase = 0;

int solve(int i, int j){
    if(vis[i][j]) return dp[i][j];
    vis[i][j] = true;
    if(i==T){
        if(j == n) return dp[i][j] = 0;
        else return dp[i][j] = INF;
    }
    int a,b = INF, c = INF;
    a = solve(i+1, j)+1;
    if(j<n && has_train[i][j][0] && i+t[j] <= T)
        b = solve(i+t[j], j+1);
    if(j>1 && has_train[i][j][1] && i+t[j-1] <= T)
        c = solve(i+t[j-1], j-1);
    return dp[i][j] = min(a,min(b,c));
}


int main(){
    while(scanf("%d", &n) != EOF && n){
    memset(has_train, false, sizeof(has_train));
    scanf("%d", &T);
    for(int i = 1; i < n; i++){
        scanf("%d", t+i);
    }
    scanf("%d", &m1);
    for(int i = 0; i < m1; i++){
        scanf("%d", d+i);
        for(int j = 1,tt = d[i]; j <= n; j++){
            has_train[tt][j][0] = true;
            tt += t[j];
        }
    }
    scanf("%d", &m2);
    for(int i = 0; i < m2; i++){
        scanf("%d", e+i);
        for(int j = n, tt = e[i]; j>0; j--){
            has_train[tt][j][1] = true;
            tt += t[j-1];
        }
    }

    memset(dp, 0, sizeof(dp));
    memset(vis, false, sizeof(vis));

    solve(0,1);

    printf("Case Number %d: ", ++kase);
    if(dp[0][1] >= INF) printf("impossible\n");
    else printf("%d\n", dp[0][1]);
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值