【问题描述】
你正在考虑用一辆搬家卡车来帮你从重庆搬到成都。鉴于当今飞涨的油价,你想知道这个大家伙要吃掉多少升油。
这辆卡车每走一公里消耗一公升汽油。油箱的容量为200公升。当你从重庆租到它的时候,油箱是半满的。当你到达成都去归还卡车的时候,油箱必须至少是半满,否则租赁公司会敲诈你更多的油费,但也不想在中途因为没有油而抛锚。
【输入格式】
第一个整数为重庆到成都的距离(单位:公里),不超过10000。
接下来至多100个加油站信息,描述途中的加油站的情况,按照距离由近到远排列。每个加油站包括距离重庆的距离(单位:公里)以及每公升油的价格(单位:1分),不超过2000
【输出格式】
输出重庆到成都最少需要花费的油费。如果在上述限制下无法从重庆到达程度,输出“Impossible”.
【输入样例】
500
100 999
150 888
200 777
300 999
400 1009
450 1019
500 1399
【输出样例】
450550
【数据范围】
出发城市和目的城市的距离不超过10000。
加油站个数不超过100,每个加油站价格不超过2000分。
【原题传送矩阵】
Code[VS]5287 原题传送矩阵
CQYZOJ P1496 原题传送矩阵
【思路梳理】
又是一个新的学期=_=,NOIP进入冲刺阶段,赶紧先码个题解压压惊。中档难度的dp题,重点在于状态转移。由于我们的最终费用、能否到达重点与油量密切相关,那么可以想到设置一个二维的数组进行动态规划。
首先考虑如何从第i-1个加油站转移到第i个加油站。令x为两个加油站间的距离=dist[i]-dist[i-1],j为离开第i个加油站时的油量,k为离开第i-1个加油站时的油量:
显然当我们离开第i-1个加油站的时候,邮箱里的油至少必须要为x,否则不能到达第i个加油站。同时,我们也要保证任何时候,油箱里的油(petrol)不能超过容量限制即200升。最后,加的油的量m必须要非负,即j要大于等于k-x。那么我们可以得出如下的取值范围:
k-x<=j<=200,0<=k<=200,k>=x,
加的油量m=j+x-k,那么在第i个加油站耗费的总费用为:m*price[i],那么可以设计出如下的状态转移方程:
d[i][j]=在离开前i个加油站的时候油箱内剩的油量为j时,所需要的最小费用
d[i][j]=min{ d[i-1][k] | 0<=j,k<=200 && k>=dist[i]-dist[i-1]} + [j+(dist[i]-dist[i-1])-k]*price[i]
几个值得提出的细节:
1.首先我们不知道在成都处是否有设置加油站。也就是说,如果没有任何一个加油站设置在成都处,那么第一个问题是不能够到达成都(汽车会在第n个加油站处停下而非成都),那么我们需要手动加入第n+1个“加油站”:成都。接着成都“加油站”的油价一定要设置为无穷大,否则汽车可以在成都处偷油(以0油价加油)。
2.同样的道理,读者可能会想到汽车在重庆偷油,但是可以不需要担心。理由如下:
1).重庆处有一个加油站,那么肯定不可能偷到油(price[1]!=0);
2).重庆处没有加油站,那么第1个加油站不是重庆,不会转移到dist[0](重庆)这个状态来。换言之,根本就不需要调用重庆的油价,状态直接从重庆向第一个加油站开始转移。
3.如果能够到达成都,那么答案d[n][100]一定不会比d[n][101]或者是后面的数更差,因为总是可以在前面某一个加油站处少加任意个单位的油来减少费用(油价一定大于0)。
下面给出cpp代码。
【Cpp代码】
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 40000005
#define maxn 105
#define petrol 205
using namespace std;
long long n,tot_dist,d[maxn][petrol],dist[maxn],price[maxn];
void dp()
{
//d[i][j]=前个加油站车里还有j升油时最小的费用
//d[i][j]= min { d[i-1][k] | 0<=j,k<=200 && k>=dist[i]-dist[i-1]}+[j+(dist[i]-dist[i-1])-k]*price[i]
//ans=d[n+1][100]
for(int i=0;i<=n+1;i++)
for(int j=0;j<=200;j++) d[i][j]=inf;
d[0][100]=0;
if(dist[n]!=tot_dist)//防止汽车在成都免费加油
{
dist[++n]=tot_dist;
price[n]=inf;
}
for(int i=1;i<=n;i++)
{
int x=dist[i]-dist[i-1];//第i个加油站和上一个加油站的距离
for(int j=0;j<=200;j++)
{
long long &t=d[i][j];
for(int k=x;k<=200;k++)if(j+x-k>=0)
t=min(t,d[i-1][k]+(j+x-k)*price[i]);
}
}
if(d[n][100]!=inf) cout<<d[n][100]<<endl;
else printf("Impossible\n");
}
int main()
{
scanf("%d",&tot_dist);
int i=1;
while(scanf("%d%d",&dist[i],&price[i])==2) i++,n++;
dp();
return 0;
}