动态规划
例题
优化青海湖至景点X的租车路线成本
小F计划从青海湖出发,前往一个遥远的景点X进行旅游。景点X可能是“敦煌”或“月牙泉”,线路的路径是唯一的。由于油价的不断上涨,小F希望尽量减少行程中的燃油成本。车辆的油箱容量为400L,在起始点租车时,车内剩余油量为 200L。每行驶 1km 消耗 1L 油。沿途设有多个加油站,小F可以在这些加油站补充燃油;此外,到达目标景点X还车的时候,需要保证车内剩余的油至少有 200L。
小F需要你帮助他计算,如果合理规划加油站的加油顺序和数量,最小化从青海湖到景点X的旅行成本(元)。
输入:
- distance:从青海湖到景点X的总距离(km),距离最远不超过 10000 km。
- n:沿途加油站的数量 (1 <= n <= 100)
- gas_stations:每个加油站的信息,包含两个非负整数 [加油站距离起始点的距离(km), 该加油站的油价(元/L)]
输出:
- 最小化从青海湖到景点X的旅行成本(元)。如果无法到达景点X,或者到达景点X还车时油料剩余不足 200L,则需要返回
-1告诉小F这是不可能的任务。
题目要点整理
起始油量为200, 油箱容量为400
每行驶 1km 消耗 1L 油
最后要求到终点油量为200
首先我们要找到问题能否转移到子问题,也就决定了本题是否可以用DP, 也就是DP问题的关键->如何设置DP数组
如果设置dp[i][j]为经过了i个加油站, 油量为j的最小成本, 即可完成状态转移
如果不加油: dp[i+1][j-dis] = min(dp[i+1][j-dis], dp[i][j]), 成本不变. 走到下一站用了dis的油的花费和上一站的花费一样
如果加油 dp[i+1][j-dis+add] = min(dp[i+1][j-dis+add], dp[i][j]+add*pirce) 成本需要增加加油的价格
加油必须要至少到达下一个加油站, 所以add+j>=dis, 由于限制了油箱大小,所以add需要限制, j+add<=MAX_CAPACITY
最后一步就是dp初始状态的定义, 油箱状态有0到400,共401种状态, 所以dp.size()=(油站数+1, 油箱容量+1), 初始化全部为INT_MAX
初始状态 dp[0][200-dis] = 0
其他处理:
- 由于给的加油站不一定是按照距离排列的, 所以需要先进行排列, 以保证加油站是按照距离由近到远访问的
- 有些加油站可能比目标距离远, 这部分不需要考虑,需要删除
- 终点需要加入一个加油站,以保证最后达到了终点
- 注意在使用python提交时修改返回值为int类型而不是字符串, Impossible为-1
c++
代码解读
复制代码
const int MAX_CAPACITY = 400;
const int INT_MAX = 1000000;
std::string solution(int distance, int n, std::vector<std::vector<int>> gasStations1) {
// Please write your code here
//按照距离排序
std::sort(gasStations1.begin(),gasStations1.end(),[](vector<int>& a, vector<int>& b){return a[0]<b[0];});
//剔除到达不了的加油站
vector<std::vector<int>> gasStations;
for(auto it = gasStations1.begin(); it!=gasStations1.end(); it++)
{
if((*it)[0]<distance)
gasStations.push_back(*it);
else
break;
}
gasStations.push_back({distance, 0}); // 加入终点, 油价无所谓,反正不会用到
int gas_n = gasStations.size();
vector<vector<int>> dp(gas_n,vector<int>(MAX_CAPACITY+1,INT_MAX));
//初始油量到达不了第一个加油站
if(gasStations[0][0]>200)
return "Impossible";
//初始化dp数组
dp[0][200-gasStations[0][0]] = 0;
for(int i = 0; i<gas_n-1; i++)
{
//加油站之间的距离
int dis = gasStations[i+1][0]-gasStations[i][0];
int price = gasStations[i][1];
//每种油量都要转移,可以转移到任意加油数的状态
for(int j = 0; j<=MAX_CAPACITY; j++)
{
if(dp[i][j]==INT_MAX) continue;
//此站可以选择不加油直接到达下一站
if(j>=dis)//不加油
dp[i+1][j-dis] = min(dp[i+1][j-dis],dp[i][j]);
//此站选择加油
for(int add = max(1, dis-j); add+j<=MAX_CAPACITY; add++)
{
dp[i+1][j+add-dis] = min(dp[i+1][j+add-dis],dp[i][j]+add*price);
}
}
}
cout<<"ans: "<<to_string(dp[gas_n-1][200])<<endl;;
return dp[gas_n-1][200]!=INT_MAX? to_string(dp[gas_n-1][200]):"Impossible";
}
递归写法, 当前站的成本由上一站成本转移来, 由于递归dfs(i,j)的写法, 无法像DP数组那样直接对dp[i][j-dis]进行操作,所以要重新考虑转移方程,有一些些绕,但是道理都一样
不加油时:dfs(i,j) = dfs(i-1, j+dis);
加油时:dfs(i,j) = dfs(i-1, origin); 其中origin为上一站的油量, 计算方式为: origin + add - dis = j
其中加油时的转移要进行一些限制, 比如add>0,origin+add<=MAX_CAPACITY, 具体代码如下
c++
代码解读
复制代码
std::string solution(int distance, int n,
std::vector<std::vector<int>> gasStations1) {
// Please write your code here
std::sort(gasStations1.begin(), gasStations1.end(),
[](vector<int> &a, vector<int> &b) { return a[0] < b[0]; });
//剔除到达不了的加油站
vector<std::vector<int>> gasStations;
for (auto it = gasStations1.begin(); it != gasStations1.end(); it++) {
if ((*it)[0] < distance)
gasStations.push_back(*it);
else
break;
}
gasStations.push_back({distance, 0}); // 加入终点, 油价无所谓,反正不会用到
int gas_n = gasStations.size();
vector<vector<int>> memo(gas_n, vector<int>(401, -1));
//初始油量到达不了第一个加油站
if (gasStations[0][0] > 200)
return "Impossible";
auto dfs = [&](auto&& dfs, int i, int j)->int
{
//初始状态只有油量为200-gasStations[0][0]的状态
if(i==0)
return j==200-gasStations[0][0]?0:INT_MAX;
int& res = memo[i][j];
if(res != -1)
return res;
int dis = gasStations[i][0] - gasStations[i-1][0];
int price = gasStations[i-1][1];
res = INT_MAX;
//不加油, 此时的状态可由不加油的状态转移过来
if (j+dis<=MAX_CAPACITY)
{
res = min(res, dfs(dfs, i-1, j+dis));
}
//加油了
//遍历原始油量 origin + add - dis = j 原始油量+加油量-消耗油量=下一站的剩余油量
for(int origin = 0; origin<MAX_CAPACITY; origin++)
{
int add = j-origin+dis;
//保证加油的量>0且加油之后不会超过容量
if(add>0&&add+origin<=MAX_CAPACITY)
res = min(res, dfs(dfs, i-1, origin)+add * price);
}
return res;
};
int ans = dfs(dfs, gas_n-1, 200);
cout<<"ans: "<<ans<<endl;
return ans == INT_MAX? "Impossible" : to_string(ans);
第一次写题解,有写的不对的的地方大家多多包涵, 只是分享一下自己的解题思路,也想借着这个机会结识更多的志同道合的小伙伴!