【Leetcode】1687. Delivering Boxes from Storage to Ports

题目地址:

https://leetcode.com/problems/delivering-boxes-from-storage-to-ports/description/

n n n个货箱组成的数组 A A A,每个货箱有两个属性 ( p , w ) (p,w) (p,w) p p p表示这个货箱要去的码头的编号(编号从 1 1 1开始), w w w表示这个货箱的重量。有个卡车,卡车载重限制 M w M_w Mw和载的货箱个数限制 M b M_b Mb。现在卡车需要按照货箱在数组中的顺序依次将所有货箱运到相应的码头。运送方式是,卡车先依次装上货箱,然后将这些货箱按照货箱在数组中的顺序,依次前往对应的码头,最后再返回仓库继续运下一批货。从仓库到某个码头,或者从码头到另一个不同的码头,或者从码头到仓库,每一段的代价都是 1 1 1。问最小代价。题目保证码头编号为正整数。

思路是动态规划。设 f [ i ] f[i] f[i]是只运送前 i i i个箱子并回到仓库的情况下所需要的最小代价, i i i 1 1 1开始计数,则 f [ 0 ] = 0 f[0]=0 f[0]=0。设 c [ k , i ] c[k,i] c[k,i]表示卡车从仓库出发,接下来运送完货物 k , . . . , n k,...,n k,...,n并回到仓库的总代价( k k k也从 1 1 1开始计数)。则 f [ i ] = min ⁡ j { f [ j ] + c [ j + 1 ] [ n ] } f[i]=\min_j \{f[j]+c[j+1][n]\} f[i]=minj{f[j]+c[j+1][n]},其中 j j j枚举的是最后一趟运送的是区间 [ j + 1 , n ] [j+1,n] [j+1,n]的货物,同时要保证货物个数不超过限制,总重量也不超过限制。最后答案即为 f [ n ] f[n] f[n]

容易看出 c [ k , i ] c[k,i] c[k,i]等于 [ k , i ] [k,i] [k,i]这一段的码头编号的变化的次数之和再加 2 2 2(出入仓库的两次代价),这可以预处理出来。设 c ′ [ k ] c'[k] c[k]是从货物 1 1 1开始到货物 k k k,码头总共变化了多少次,那么 c ′ [ 1 ] = 0 c'[1]=0 c[1]=0 c ′ [ k ] = c ′ [ k − 1 ] + d c'[k]=c'[k-1]+d c[k]=c[k1]+d d d d是第 k k k个货物所在码头和第 k − 1 k-1 k1个货物所在码头是否不同,如果不同则取 1 1 1,否则取 0 0 0。那么 c [ j + 1 ] [ n ] = c ′ [ i ] − c ′ [ j + 1 ] + 2 c[j+1][n]=c'[i]-c'[j+1]+2 c[j+1][n]=c[i]c[j+1]+2,所以 f [ i ] = min ⁡ j { f [ j ] + c ′ [ i ] − c ′ [ j + 1 ] + 2 } f[i]=\min_j \{f[j]+c'[i]-c'[j+1]+2\} f[i]=minj{f[j]+c[i]c[j+1]+2}。我们稍作变换,得: f [ i ] = min ⁡ j ∈ I { ( f [ j ] − c ′ [ j + 1 ] ) + c ′ [ i ] + 2 } f[i]=\min_{j\in I} \{(f[j]-c'[j+1])+c'[i]+2\} f[i]=jImin{(f[j]c[j+1])+c[i]+2}其中 c ′ [ i ] + 2 c'[i]+2 c[i]+2是个定值, j j j取自某个区间 i i i,并且这个区间只会整体向右移动而不会左移,那么这个问题就变为了滑动窗口求最小值的问题,可以用单调队列。单调队列里维护货物是第几个(即货物下标,从 1 1 1开始计数),保证队头是使得 f [ j ] − c ′ [ j + 1 ] f[j]-c'[j+1] f[j]c[j+1]最小且 j j j在合法区间里的那个 j j j。那么每次求 j j j只需要取队头即可。

代码如下:

class Solution {
 public:
  using ll = long long;
  const int INF = 2e9;
  int boxDelivering(vector<vector<int>>& bs, int _, int maxB, int maxW) {
    int n = bs.size();
    vector<ll> w(n + 1, 0), c(n + 2, 0);
    // w是货物重量的前缀和,即w[i]是前i个货物的总重量
    // c[i]是从第1个货物开始到第i个货物为止码头的变化次数
    for (int i = 1; i <= n; i++) w[i] = w[i - 1] + bs[i - 1][1];
    for (int i = 2; i <= n; i++)
      c[i] = c[i - 1] + (bs[i - 1][0] != bs[i - 2][0]);
    // 不运送货物的话,总代价是0
    vector<int> f(n + 1);
    deque<int> q;
    // 要预先把下标0存进去,因为f[0] - c[1]也是一个合法的项
    q.push_back(0);
    for (int i = 1, j = 0; i <= n; i++) {
      // 将总数量超了或者总重量超了的下标出队
      while (q.size() && (i - q.front() > maxB || w[i] - w[q.front()] > maxW))
        q.pop_front();
      f[i] = f[q.front()] + c[i] - c[q.front() + 1] + 2;
      while (q.size() && f[i] - c[i + 1] <= f[q.back()] - c[q.back() + 1])
        q.pop_back();
      q.push_back(i);
    }

    return f[n];
  }
};

时空复杂度 O ( n ) O(n) O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值