斜率優化動態規劃可以用來解決這道題。同時這也是一道經典的斜率優化基礎題。
分析:明顯是動態規劃。令 爲前 個裝箱的最小花費。
轉移方程如下:
用 表示前 個容器的長度之和(即 的前綴和),方程簡化爲:
又令 爲 ,繼續簡化方程爲:
暴力dp是 ,考慮優化。如何優化,就是用前面所提到的斜率優化。這玩意到底是什麼?我們先來繼續對狀態轉移方程進行進一步的推導。
對於每個 可以知道都是由一個 推過來的。這個 對於當前的 是最優的決策。假設現在有兩個決策 ,且決策 優於 ,則有:
拆開可得:
化簡可得:
即:
令 ,可得:
也就是說,若 滿足上面這個式子,那麼 一定比 優。
爲什麼叫斜率優化?因爲上面這個式子可以把看作 看做縱座標, 看做橫座標,上面的等式右側就相當於 也就是一個一次函數的斜率。當這個斜率 則 優於 。
假如我們有三個決策 (如下圖)
容易證明: 不可能是最優的。
這樣一來,每兩個決策間的斜率便是單調上升的。
所以有兩種做法:
- 對於 ,有了斜率單調上升這個條件,就可以去二分最優的決策點(也就是斜率小於 的)。複雜度 。
- 又因爲 是單調遞增的,可以用單調隊列來維護。具體實現就是,把決策放進一個單調隊列裏,如果隊首和當前的 間的斜率 ,就把隊首刪掉(即h++)。對於隊尾,就每次把加入 後不滿足斜率單調上升的隊尾全部刪掉(即t–),最後把 放進單調隊列就好了。
注意事項:
long long.
還有 而不是 !!!!(因爲這個我調了很久)
給一下單調隊列做法的代碼:
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
const int MAXN = 50050;
int N, L, dp[MAXN], sum[MAXN], f[MAXN], g[MAXN], h, t, Q[MAXN];
inline double xie(int j1, int j2)
{
return (double) (dp[j2] + g[j2] - dp[j1] - g[j1]) / (f[j2] - f[j1]);
}
#undef int
int main()
{
scanf("%d%d", &N, &L);
for(int i = 1; i <= N; i++)
{
scanf("%d", &sum[i]);
sum[i] += sum[i - 1];
f[i] = sum[i] + i;
g[i] = (f[i] + L + 1) * (f[i] + L + 1);
}
g[0] = (L + 1) * (L + 1); //important!!!
for(int i = 1; i <= N; i++)
{
while(h < t && xie(Q[h], Q[h + 1]) <= 2 * f[i]) h++;
dp[i] = dp[Q[h]] + (f[i] - f[Q[h]] - L - 1) * (f[i] - f[Q[h]] - L - 1); //更新dp值
while(h < t && xie(Q[t], i) < xie(Q[t - 1], Q[t])) t--;
Q[++t] = i;
}
printf("%lld\n", dp[N]);
return 1;//防抄
}