简要题意:
有 n n n 个路灯,你一开始在第 c c c 个路灯的位置。每个路灯都有 L i L_i Li (距离)。每过 1 s 1s 1s,第 i i i 个路灯就会产生 P i P_i Pi 的消耗,你可以走 1 1 1 个单位距离。你可以花 0 s 0s 0s 的时间关掉路灯,关掉路灯之后路灯不会再有消耗。你需要求出:最小的消耗是多少?
1 ≤ n ≤ 50 1 \leq n \leq 50 1≤n≤50,是不是很简单?
我们首先已经有了:
O ( 2 n ⋅ n ) \mathcal{O}(2^n \cdot n) O(2n⋅n) 的算法,大力枚举关路灯的顺序,计算消耗即可。
显然 n ≤ 50 n \leq 50 n≤50 是个误导,我们直接把数据范围改成:
1 ≤ n ≤ 3 × 1 0 3 1 \leq n \leq 3 \times 10^3 1≤n≤3×103.
这下仔细一考虑,典型的 区间 dp \text{dp} dp 啊!
用 f i , j f_{i,j} fi,j 表示 [ i , j ] [i,j] [i,j] 的答案, s s s 表示 P P P 的前缀和(老套路了)。
但是你会发现问题来了:你不知道 f i , j f_{i,j} fi,j 你是从 i + 1 → i i+1 \rightarrow i i+1→i 还是 j − 1 → j j-1 \rightarrow j j−1→j 还是 i → j i \rightarrow j i→j 还是 j → i j \rightarrow i j→i.
你不知道你现在的位置在 i i i 还是 j j j,无法计算啊!
所以,用 f i , j , 0 f_{i,j,0} fi,j,0 表示 [ i , j ] [i,j] [i,j] 且当前在 i i i 的答案, f i , j , 1 f_{i,j,1} fi,j,1 是在 1 1 1 的答案。
诚然你会说:可能不在 i , j i,j i,j,在区间中间。
不对。因为你最后关掉的路灯一定是 i i i 或者 j j j,在去中间纯属是浪费时间。
如何设置状态转移方程?如下:
{ f i , j , 0 = min ( f i + 1 , j , 0 + ( L i + 1 − L i ) ⋅ ( s n − s j + s i ) , f i + 1 , j , 1 + ( L j − L i ) ⋅ ( s n − s j + s i ) ) f i , j , 1 = min ( f i , j − 1 , 0 + ( L j − L i ) ⋅ ( s n − s j − 1 + s i − 1 ) , f i , j − 1 , 1 + ( L j − L j − 1 ) ⋅ ( s n − s j − 1 + s i − 1 ) ) \begin{cases} f_{i,j,0} = \min(f_{i+1,j,0} + (L_{i+1} - L_i) \cdot (s_n - s_j + s_i) , f_{i+1,j,1} + (L_j - L_i) \cdot (s_n - s_j + s_i)) \\ f_{i,j,1} = \min(f_{i,j-1,0} + (L_j - L_i) \cdot (s_n - s_{j-1} + s_{i-1}) , f_{i,j-1,1} + (L_j - L_{j-1}) \cdot (s_n - s_{j-1} +s_{i-1})) \\ \end{cases} {fi,j,0=min(fi+1,j,0+(Li+1−Li)⋅(sn−sj+si),fi+1,j,1+(Lj−Li)⋅(sn−sj+si))fi,j,1=min(fi,j−1,0+(Lj−Li)⋅(sn−sj−1+si−1),fi,j−1,1+(Lj−Lj−1)⋅(sn−sj−1+si−1))
每个方程的形式都是 当前状态 = 前一个状态 + 所需关灯时间 ⋅ 区间之外其它灯 1s 的消耗 \text{当前状态} = \text{前一个状态} + \text{所需关灯时间} \cdot \text{区间之外其它灯 1s 的消耗} 当前状态=前一个状态+所需关灯时间⋅区间之外其它灯 1s 的消耗.
最后答案即 min ( f 1 , n , 0 , f 1 , n , 1 ) \min(f_{1,n,0} , f_{1,n,1}) min(f1,n,0,f1,n,1).
时间复杂度: O ( n 2 ) \mathcal{O}(n^2) O(n2).
实际得分: 100 p t s 100pts 100pts.
for(i=1;i<=n;i++) s[i] = s[i-1] + P[i];
for(l=1;l<=n;l++)
for(int i=1,j;(j=i+l-1)<=n;i++)
if(i<=c && c<=j) { // c 必须在区间里
f[i][j][0]=min(f[i+1][j][0] + (L[i+1] - L[i]) * (s[n] - s[j] + s[i]),f[i+1][j][1] + (L[j] - L[i]) * (s[n] - s[j] + s[i]));
f[i][j][1]=min(f[i][j-1][0] + (L[j] - L[i]) * (s[n] - s[j-1] + s[i-1]),f[i][j-1][1] + (L[j] - L[j-1]) * (s[n] - s[j-1] + s[i-1]));
}else f[i][j][0] = f[i][j][1] = INF; //其余情况赋极大值