1. 程式人生 > >[BZOJ1010][HNOI2008]玩具裝箱toy

[BZOJ1010][HNOI2008]玩具裝箱toy

計算 odi 優化 bre code 註意 std -c tdi

斜率優化DP首題,多寫一點吧。

Step 1 寫出DP方程

\(f_i\)\(i\)點時的最優方案,\(s_i\)\(i\)\(C_i\)前綴和,則有\(f_i = min\{f_j+(i-j-1+s_i-s_j-l)^2\mid 0\le j < i-1\}\)

Step 2 證明決策單調性

不要被這個“決策單調性”給唬住,其實就是證明:若在計算\(f_i\)的時候,選\(j\)比選\(k\)好,那麽之後永遠都有選\(j\)比選\(k\)好。

證明:
首先設\(t_x = s_x+x,c = l+1\)\(j > k\),選\(j\)比選\(k\)好,簡化之後的表示
\(f_j+(t_i-t_j-c)^2 < f_k + (t_i-t_j-c)^2\)


\(\Leftrightarrow f_j+t_i^2+t_j^2+c^2-2t_it_j+2t_jc-2t_ic < f_k+t_i^2+t_k^2+c^2-2t_it_k+2t_kc-2t_ic\)
\(\Leftrightarrow f_j+t_j^2-2t_it_j+2t_jc - f_k - t_k^2 + 2t_it_k - 2t_kc < 0\)
\(\Leftrightarrow f_j + t_j^2+ 2t_jc - f_k - t_k^2 - 2t_kc < 2t_it_j - 2t_it_k\)
由於\(t\)是單調上升的,所以\(2t_j-2t_k>0\)
,所以
\(\mbox{原式}\Leftrightarrow \displaystyle\frac{f_j + t_j^2+ 2t_jc - f_k - t_k^2 - 2t_kc}{2t_j - 2t_k} < t_i\)
又因為\(t\)是單調上升的,所以一旦滿足了這個條件,選\(j\)永遠比選\(k\)好。

Step 3 構造斜率形式

這一步其實就是看分子分母,化出和\(j\)\(k\)有關的式子,就可以了。

\(F_x = t_x^2+f_x+2ct_x\)\(G_x=2t_x\)
則原式可化成\(\displaystyle\frac{F_j-F_k}{G_j-G_k}<t_i\)

,由於\(G\)單調增,不等號左邊的式子(上下同乘\(-1\)後)就是在平面\(GOF\)上的一條直線\((k,j)\)的斜率(記為\(K_{k,j}\))。

Step 4 證明斜率單調

\(x, y, z\)滿足\(x < y < z\),且\(K_{x,y}>K_{y,z}\),則選\(y\)不可能更優。

證明:考慮反證法,若\(y\)優於\(x\)\(z\),則有:
\(\displaystyle\frac{F_z-F_y}{G_z-G_y}>t_i\)(這個式子其實是將Step2中的\(j>k\)反過來推出來的)
\(\displaystyle\frac{F_y-F_x}{G_y-G_x}<t_i\)
\(K_{y,z}=\displaystyle\frac{F_z-F_y}{G_z-G_y}>\displaystyle\frac{F_y-F_x}{G_y-G_x}=K_{x,y}\)與題設矛盾。

這裏應該先考慮\(y\)優於\(x\)\(z\),推出式子後得出斜率的單調結論。

Step 5 單調隊列優化DP

單調隊列是一個雙端隊列。

由於Step2,3,若在\(i\)處有\(K_{k,j}<t_i\)(\(k, j\)位於隊首),則應該令\(k\)出隊,反復執行直至隊列中只有一個元素或不滿足條件為止,此時隊首即為決策。

當計算出\(f_i\)後,利用Step4的結論,在隊尾不斷出隊直至直至隊列中只有一個元素或不滿足條件為止,並將\(i\)放入隊尾。

Step 6 Coding

貼上我寫的巨醜無比的代碼:

#include <cstdio>
#include <cmath>

typedef long long LL;
const int N = 50000 + 10;
const double eps = 1e-10;

int n, l;
LL C[N], f[N], s[N], t[N], c, F[N], G[N];
int q[N], qhd, qtl;

inline double K(int x, int y) {
    return 1.0 * (F[x] - F[y]) / (G[x] - G[y]); 
}

inline int dcmp(double x) {
    if (fabs(x) < eps) return 0;
    else if (x < 0) return -1;
    else return 1;
}

int main () {
    // input
    scanf("%d%d", &n, &l);
    for (int i = 1; i <= n; ++i) scanf("%lld", &C[i]);
    // init
    c = l + 1;
    for (int i = 1; i <= n; ++i) {
        s[i] = s[i-1] + C[i];
        t[i] = s[i] + i;
        G[i] = 2 * t[i];
    }
    // dp 
    f[0] = 0;
    q[qtl++] = 0;
    for (int i = 1; i <= n; ++i) {
        while (qhd < qtl - 1) {
            if (dcmp(K(q[qhd], q[qhd+1]) - t[i]) == -1) qhd++;
            else break;
        }
        int j = q[qhd];
        f[i] = f[j] + (t[i] - t[j] - c) * (t[i] - t[j] - c);
        F[i] = t[i] * t[i] + f[i] + 2 * c * t[i];  // mark
        while (qhd < qtl - 1) {
            if (dcmp(K(q[qtl-1], q[qtl-2]) - K(q[qtl-1], i)) == 1) qtl--;
            else break;
        }
        q[qtl++] = i;
    }   
    printf("%lld\n", f[n]);
    return 0;
}

這個題是我寫的斜率優化DP的第一題,感覺斜率優化很模板化,只需要一點點推就行了,要註意的一點是斜率優化的復雜度\(O(n)\)的,另外,演草時要好好寫字,否則真的會看不清...(例如mark處我一開始寫的是+t[i]

[BZOJ1010][HNOI2008]玩具裝箱toy