題解 P3195 【[HNOI2008]玩具裝箱TOY】

斜率優化動態規劃可以用來解決這道題。同時這也是一道經典的斜率優化基礎題。

分析:明顯是動態規劃。令dp[i] 爲前i 個裝箱的最小花費。
轉移方程如下:

dp[i]=min0j<i{dp[j]+(k=j+1iCk+ij1L)2}

sum[i] 表示前i 個容器的長度之和(即C 的前綴和),方程簡化爲:

dp[i]=min0j<i{dp[j]+(sum[i]sum[j]+ij1L)2}

又令f[i]sum[i]+i ,繼續簡化方程爲:

dp[i]=min0j<i{dp[j]+(f[i]f[j]1L)2}

暴力dp是O(n2) ,考慮優化。如何優化,就是用前面所提到的斜率優化。這玩意到底是什麼?我們先來繼續對狀態轉移方程進行進一步的推導。

對於每個dp[i] 可以知道都是由一個j0 推過來的。這個j0 對於當前的i 是最優的決策。假設現在有兩個決策j1,j2(1j1<j2<i) ,且決策j2 優於j1 ,則有:

dp[j1]+(f[i]f[j1]1L)2dp[j2]+(f[i]f[j2]1L)2

拆開可得:

dp[j1]+f[i]22f[i](f[j1]+1+L)+(f[j1]+L+1)2dp[j2]+f[i]22f[i](f[j2]+1+L)+(f[j2]+L+1)2

化簡可得:

2f[i](f[j2]+1+L)2f[i](f[j1]+1+L)dp[j2]+(f[j2]+1+L)2(dp[j1]+(f[j1]+1+L)2)

即:

2f[i]dp[j2]+(f[j2]+1+L)2(dp[j1]+(f[j1]+1+L)2)f[j2]f[j1]

g[i]=(f[i]+L+1)2 ,可得:
2f[i]dp[j2]+g[j2](dp[j1]+g[j1])f[j2]f[j1]

也就是說,若j1,j2 滿足上面這個式子,那麼j2 一定比j1 優。

爲什麼叫斜率優化?因爲上面這個式子可以把看作dp[i]+g[i] 看做縱座標,f[i] 看做橫座標,上面的等式右側就相當於ΔyΔx=k 也就是一個一次函數的斜率。當這個斜率k2f[i]j2 優於j1

假如我們有三個決策j1,j2,j3 (如下圖)

容易證明:j2 不可能是最優的。
這樣一來,每兩個決策間的斜率便是單調上升的

所以有兩種做法:

  • 對於dp[i] ,有了斜率單調上升這個條件,就可以去二分最優的決策點(也就是斜率小於2f[i] 的)。複雜度O(nlogn)
  • 又因爲f[i] 是單調遞增的,可以用單調隊列來維護。具體實現就是,把決策放進一個單調隊列裏,如果隊首和當前的i 間的斜率 <f[i] ,就把隊首刪掉(即h++)。對於隊尾,就每次把加入i 後不滿足斜率單調上升的隊尾全部刪掉(即t–),最後把i 放進單調隊列就好了。

注意事項:
long long.
還有 g[0]=(L+1)2 而不是0 !!!!(因爲這個我調了很久)

給一下單調隊列做法的代碼:

#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//防抄
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章