放在前面:本方法时间复杂度为 O ( n W ) O(nW) O ( nW ) ,单调队列优化 DP。本题还有一道弱化版 ,欢迎大佬切完这题顺便把那题也切了,并欢迎大家来该题题解看我的另一篇博客 。
一. 变量声明:
W W W :代替题目中的 K K K ,到家时应带的饲料吨数,相当于背包中的背包最大承重(weight)。
n n n :代替题目中的 N N N ,商店数量,相当于背包问题中的物品数。
x i x_i x i :同题目中的 x i x_i x i 。
w i w_i w i :代替题目中的 f i f_i f i ,商店 i i i 食物数量。
v i v_i v i :代替题目中的 c i c_i c i ,商店 i i i 食物单价。
二. 思路
1. 思考解法
路线上后面的商店不会影响路线前半部分的最优解,符合无后效性 。
有最大容量 W W W ,有可选择的物品,每个物品有重量和价值,明显是背包类问题。
所以考虑背包 DP。
2. 初始状态
d i , j = { 0 ( i = 0 ∧ j = 0 ) ∞ ( e l s e ) d_{i,j}=\begin{cases} 0&(i=0~\land~j=0)\\ \infty&(else) \end{cases} d i , j = { 0 ∞ ( i = 0 ∧ j = 0 ) ( e l se )
3. 结束状态
d home , W d_{\operatorname{home},W} d home , W (home \operatorname{home} home :见代码和代码中的注释)
4. 确定状态转移方程
d i , j d_{i,j} d i , j :已经经过前 i i i 个商店(已到 i i i 号商店买了东西但还没有往 i + 1 i+1 i + 1 号走)且恰好一共买了 j j j 份食物时最少花费的费用。
本题就是一道改装版的多重背包问题 ,就加了一个转移花费,转移花费怎么求呢?i i i 店与上一家店距离差为 x i − x i − 1 x_i-x_{i-1} x i − x i − 1 ,若在 i i i 号店之前买的食物份数一共为 k k k ,在 i i i 店购物后车上的食物份数为 j j j ,则这段路上车辆运送的食物数为 k k k 。转移花费就是 ( x i − x i − 1 ) × k 2 (x_i-x_{i-1})\times k^2 ( x i − x i − 1 ) × k 2 。
综上所述,朴素状转方程:d i , j = min k = 0 k ⩽ j { d i − 1 , k + ( x i − x i − 1 ) × k 2 + v i × ( j − k ) } d_{i,j}=\min_{k=0}^{k\leqslant j}\left\{d_{i-1,k}+(x_i-x_{i-1})\times k^2+v_i\times (j-k)\right\} d i , j = min k = 0 k ⩽ j { d i − 1 , k + ( x i − x i − 1 ) × k 2 + v i × ( j − k ) }
5. 单调队列优化(重点)
枚举 i i i 复杂度为 O ( n ) O(n) O ( n ) ,枚举 j j j 复杂度为 O ( W ) O(W) O ( W ) ,枚举 k k k 最坏情况下复杂度也是 O ( W ) O(W) O ( W ) 。总复杂度 O ( n W 2 ) O(nW^2) O ( n W 2 ) 显然会超。那么让我们观察一下状转方程:
d i , j = min k = 0 k ⩽ j { d i − 1 , k + ( x i − x i − 1 ) × k 2 + v i × ( j − k ) } ~~~~~~~~d_{i,j}=\min_{k=0}^{k\leqslant j}\left\{d_{i-1,k}+(x_i-x_{i-1})\times k^2+v_i\times (j-k)\right\}
d i , j = k = 0 min k ⩽ j { d i − 1 , k + ( x i − x i − 1 ) × k 2 + v i × ( j − k ) }
⟹ d i , j = min k = 0 k ⩽ j { d i − 1 , k + ( x i − x i − 1 ) × k 2 − v i × k + v i × j } \Longrightarrow d_{i,j}=\min_{k=0}^{k\leqslant j}\left\{d_{i-1,k}+(x_i-x_{i-1})\times k^2-v_i\times k+v_i\times j\right\}
⟹ d i , j = k = 0 min k ⩽ j { d i − 1 , k + ( x i − x i − 1 ) × k 2 − v i × k + v i × j }
重点一 :观察上面这个拆了个括号的方程,左边是我们要去求的状态,在该状态下,i i i 和 j j j 是已知的,因为该状态就是由 i i i 和 j j j 定义的,i i i 和 j j j 在一个具体的状态下为常量(或者换一种解释:i i i 和 j j j 是用 for
循环枚举出来的,所以我们当然知道他的值)。所以我们可以将 v i × j v_i\times j v i × j 提出括号。可得:
⟹ d i , j = min k = 0 k ⩽ j { d i − 1 , k + ( x i − x i − 1 ) × k 2 − v i × k } + v i × j \Longrightarrow d_{i,j}=\min_{k=0}^{k\leqslant j}\left\{d_{i-1,k}+(x_i-x_{i-1})\times k^2-v_i\times k\right\}+v_i\times j
⟹ d i , j = k = 0 min k ⩽ j { d i − 1 , k + ( x i − x i − 1 ) × k 2 − v i × k } + v i × j
重点二 :这个方程中的变量只有 k k k ,而又有 k ⩽ j k\leqslant j k ⩽ j ,因为在 i i i 店购物后的食物数不可能低于购物前。那么我们就可以使用单调队列 优化。单调队列中存放的就是 min \min min 内的部分 d i − 1 , k + ( x i − x i − 1 ) × k 2 − v i × k d_{i-1,k}+(x_i-x_{i-1})\times k^2-v_i\times k d i − 1 , k + ( x i − x i − 1 ) × k 2 − v i × k (在这里我们把它称作 c a l c i , k calc_{i,k} c a l c i , k ),对于每一个 i i i ,将 j j j 从 0 0 0 到 W W W 枚举一遍,对于每个 j j j 先将它作为 k k k 计算 c a l c i , k calc_{i,k} c a l c i , k 并放到单调队列中,再先计算状态 d i . j d_{i.j} d i . j 。
三. 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <bits/stdc++.h> using namespace std;#define re register #define int long long const int MAXn = 500 ;const int MAXW = 10000 ;template <class T > inline void read (T &a) { register char c;while (c = getchar (), c < '0' || c >'9' );register T x (c - '0' ) ;while (c = getchar (), c >= '0' && c <= '9' )x = (x << 1 ) + (x << 3 ) + c - '0' ;a = x; } template <class T >inline T min (T a, T b) { return a < b ? a : b; } struct Shop { int x; int w; int v; Shop (): x (0 ), w (0 ), v (0 ){} Shop (int x_, int w_, int v_): x (x_), w (w_), v (v_) {} inline bool operator < (Shop &a) { return this ->x < a.x; } }; Shop shop[MAXn + 10 ]; int W, n, d[MAXn + 10 ][MAXW + 10 ];int head, tail, que[MAXW + 10 ];inline int calc (int i, int k) { return d[i - 1 ][k] + (shop[i].x - shop[i - 1 ].x) * k * k - shop[i].v * k; } signed main () { int E; read (W), read (E), read (n); shop[0 ] = Shop (0 , 0 , 0 ); for (re int i = 1 , x, w, v; i <= n; ++i) { read (x), read (w), read (v); shop[i] = Shop (x, w, v); } shop[n + 1 ] = Shop (E, 0 , 0 ); sort (shop, shop + n + 2 ); memset (d, 0x3f , sizeof (d)); d[0 ][0 ] = 0 ; for (re int i = 1 ; i <= n + 1 ; ++i) { head = 1 ; tail = 0 ; for (re int j = 0 ; j <= W; ++j) { while (calc (i, que[tail]) > calc (i, j) && head <= tail) --tail; if (j - que[head] > shop[i].w && head <= tail) ++head; que[++tail] = j; d[i][j] = calc (i, que[head]) + shop[i].v * j; } } printf ("%lld" , d[n + 1 ][W]); }