豆包MarsCode AI 刷题

153 阅读6分钟

动态规划

例题

优化青海湖至景点X的租车路线成本

优化青海湖至景点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);

第一次写题解,有写的不对的的地方大家多多包涵, 只是分享一下自己的解题思路,也想借着这个机会结识更多的志同道合的小伙伴!