[BZOJ1010][HNOI2008]玩具裝箱toy
斜率優化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\)
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