题解 P3399 【丝绸之路】

· · 题解

动态规划(两个版本)

先分析一下题目。

  1. 所有城市连起来是一条链。

  2. 每一天有走和不走两种选择。

仅凭这两点,我们就很容易想到dp的思路。我写了两个版本,一个是龟速版,总用时2000多ms;一个是飞速版,总用时8ms......

先说说龟速的把。设状态f[i][j]表示第j天 到达 第i个城市的最小疲劳值,也就是说这一天你必须是刚好从前一个城市走到这里来的。状态转移方程就很容易推出来了,就是找上一个城市在i-1..j-1天里哪天到达的疲劳值最小,然后再加上这一天从上一个城市到达这一个城市的疲劳值。为什么是i-1..j-1而不是1..j-1呢?因为第i-1个城市最早只能在第i-1天到达,继续找前面的完全就没有必要。想一想,第3个城市能在第2天到达吗?当然不行。

上述文字用公式表达就是这个样子:

f[i][j]=min{f[i-1][i-1..j-1]}+D[i]\*C[j]

为什么这个公式就可以成立呢?因为你找到了哪一天到达上一个城市的疲劳值最小,你就可以不停地休息(休息是不会增加疲劳值的),一直休息到第j天,再走过来,这样就是最优的了。

代码:

    #include<iostream>
    #include<cstdio>
    #include<fstream>
    #include<algorithm>
    #include<string>
    #include<sstream>
    #include<cstring>
        using namespace std;
        const int INF=2139063143;
        int D[1002],C[1002],f[1002][1002];
    int main()
    {
        memset(f,0x7f,sizeof(f));
        int N=0,M=0;
        scanf("%d%d",&N,&M);
        for(int i=1;i<=N;i++) scanf("%d",&D[i]);
        for(int i=1;i<=M;i++) 
        {
            scanf("%d",&C[i]);
            f[1][i]=D[1]*C[i];//初始化第一个城市的
        }
        for(int i=2;i<=N;i++)
            for(int j=i;j<=M;j++)
            {
                int Min=INF;
                //找最小
                for(int k=j-1;k>=i-1;k--) Min=min(Min,f[i-1][k]);
                f[i][j]=Min+D[i]*C[j];//更新
            }
        int ans=INF;
        for(int i=N;i<=M;i++) ans=min(ans,f[N][i]);
        printf("%d",ans); 
        return 0;
    }

接着再说一下快速的吧。设状态f[i][j]表示第j天 位于 第i个城市的最小疲劳值,也就是说这一天你可以不是刚好从前一个城市走到这里来的,也可以是前几天已经到了,休息到今天的。再看题目,对于每一天都有走和不走两种选择,状态转移方程就出来了。对于在第i个城市的第j天,你可以在j-1天从上一个城市走过来(第1种选择),也可以休息不走(第2种选择)。这里和龟速版的意思一样,也是看一看从上一个城市的哪一天走过来最优,只不过可以休息。

上述文字用公式表达就是这样子:

f[i][j]=min{f[i][j-1],f[i-1][j-1]+D[i]\*C[j]}

其中f[i][j-1]是休息,f[i-1][j-1]+D[i]*C[j]是走。

代码:

    #include<iostream>
    #include<cstdio>
    #include<fstream>
    #include<algorithm>
    #include<string>
    #include<sstream>
    #include<cstring>
        using namespace std;
        const int INF=2139063143;
        int D[1002],C[1002],f[1002][1002];
    int main()
    {
        int N=0,M=0;
        scanf("%d%d",&N,&M);
        for(int i=1;i<=N;i++) scanf("%d",&D[i]);
        for(int i=1;i<=M;i++) scanf("%d",&C[i]); 
        memset(f,0x7f,sizeof(f));
        for(int i=0;i<=M;i++) f[0][i]=0;
        for(int i=1;i<=N;i++)
            for(int j=i;j<=M;j++)
                f[i][j]=min(f[i][j-1],f[i-1][j-1]+D[i]*C[j]);
        int ans=INF;
        for(int i=N;i<=M;i++) ans=min(ans,f[N][i]);
        printf("%d",ans); 
        return 0;
    }

两种方案,个人感觉第一种好理解一点,但速度真的超级慢啊,大家自行取舍吧。