题解: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;
}
·注意
前缀最小值部分一定不要写反遍历顺序,这可让我废了两年半才找出来的问题!