题解:ABC320F - Fuel Round Trip

题解:ABC320F - Fuel Round Trip

·题目

链接:Atcoder

链接:洛谷

·难度

算法难度:B。

思维难度:A。

调码难度:A。

综合评价:普及+/提高(个人认为评为提高+/省选-是合理的)。

·算法

动态规划。

·思路

状态:f[i][j][k]表示携带j升油从i号点走到n号点并走回i号点剩余k升汽油,这种情况需要的最小花费。

初值:对于任何f[n][x][x]的初始值都为0,从n到n再到n相当于白玩,自然汽油数量也不会改变,更不会有支出。

转移:方便起见,先定义几个初始值:

long long a=mx,b=mx,c=mx,d=x[i+1]-x[i];

long long u=j-d,v=k+d;

long long u_=min(h,j+tf[i])-d,v_=max(0LL,k-tf[i])+d;

注意,这里的tf是指题目里面的f,因为dp函数里面用到f,改名为tf。

要求f[i][j][k],从3方面考虑:

①不在i号点购买汽油,f[i+1][u][v]。

②去程在i号点购买汽油,f[i+1][u_][v]。

③返程在i号点购买汽油,f[i+1][u][v_]。

如果某个方案没有越界,就可以用它与原状态取最小值更新答案。

但是我们发现,这几种方案都是假设把汽油都加满的情况,并没有考虑丢弃汽油使得总体情况最优的事件,因此我们采用一种神奇的方式来优化答案:前缀最小值。

具体来说,对于一个状态f[i][j][k],一定是j越小、k越大汽油越少,因此从小往大遍历j、从大往小遍历k,对于每个f[i][j][k],都用f[i][j-1][k]、f[i][j][k+1]、f[i][j-1][k+1]与原状态取最小值更新(主要是为了避免状态在转移过程中由于目测不够优而被遗忘)。

返回:任何一个f[0][x][y]都可以作为答案。

·代价

O(nh2),遍历每个状态就这么大……

·细节

对于边界处理,即越界访问的转移被跳过,我们采用与极值(0、h)比较大小实现。

·代码

#include<bits/stdc++.h>
#define H 330
#define N 330
using namespace std;
long long f[N][H][H]={},tf[N]={},tp[N]={},x[N]={},ans=0,h=0,n=0;
//f:动态规划数组;tf、tp:题目中的f和p;x、n、h:同题意;ans:答案
int main(){
    scanf("%lld%lld",&n,&h);
    for(long long i=1;i<=n;i++){
        scanf("%lld",&x[i]);
    }
    for(long long i=1;i<n;i++){
        scanf("%lld%lld",&tp[i],&tf[i]);
    }
    //输入
    memset(f,127,sizeof(f));
    long long mx=f[0][0][0];
    for(long long i=0;i<=h;i++){
        f[n][i][i]=0;
    }
    //初始值
    ans=mx;
    //答案最初设为一个很大的树,这里采用memset赋值中的一个很大的数
    for(long long i=n-1;i>=0;i--){
        for(long long j=0;j<=h;j++){
            for(long long k=0;k<=h;k++){
                long long a=mx,b=mx,c=mx,d=x[i+1]-x[i];
                //a:①转移;b:②转移;c:③转移;d:该点与下一个点之间的距离
                long long u=j-d,v=k+d;
                long long u_=min(h,j+tf[i])-d,v_=max(0LL,k-tf[i])+d;
                //分别存储不消费时的汽油量(不带下划线)和购买后的汽油量(带下划线)
                if(u>=0&&v<=h){
                    a=f[i+1][u][v];
                }
                if(u_>=0&&v<=h){
                    b=f[i+1][u_][v]+tp[i];
                }
                if(u>=0&&v_<=h){
                    c=f[i+1][u][v_]+tp[i];
                }
                //三种转移
                f[i][j][k]=min(a,min(b,c));
                //更新状态
            }
        }
        for(long long j=1;j<=h;j++){
            for(long long k=h;k>=1;k--){
                f[i][j][k]=min(f[i][j][k],min(f[i][max(0LL,j-1)][min(h,k+1)],min(f[i][max(0LL,j-1)][k],f[i][j][min(h,k+1)])));
            }
        }
        //前缀最小值
    }
    for(long long i=0;i<=h;i++){
        for(long long j=0;j<=h;j++){
            ans=min(ans,f[0][i][j]);
        }
    }
    //在所有f[0][x][y]中选答案
    if(ans==mx){
        ans=-1;
    }
    printf("%lld\n",ans);
    //特判-1、输出答案
    return 0;
}

·注意

前缀最小值部分一定不要写反遍历顺序,这可让我废了两年半才找出来的问题!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值