演算法淺談之斜率優化
阿新 • • 發佈:2020-07-23
嘗試拿一道例題(玩具裝箱)來講明白斜率優化。
-
1) 題目大意
有\(n\)個物品,每個物品有一個體積\(c_i\),你可以製作無限個箱子,假設你製作的箱子長度為\(x\),那麼你所需花費的費用就是\((x-L)^2\),現在要求出把\(n\)個物品都放入箱子裡的最小費用。
-
2) 普通DP
由題意我們很快就可以得出一個DP式:
\[f_i=min(f_j+(s_i-s_j+i-j-1-L)^2) \]
暴力求\(f_i\)時間複雜度\(O(n^2)\)
-
3) 斜率優化
我們觀察式子,把\(min\)去掉,式子變成
\[f_i=f_j+(s_i-s_j+i-j-1-L)^2 \]
我們把固定的設為\(A(i)\),不定的設為\(B(i)\)(與\(j\)有關的算不固定值,其餘算固定值)
那麼
\[A(i)=s_i+i-1-L \]
\[B(i)=s_i+i \]
原式變為
\[f_i=f_j+(A(i)-B(j))^2 \]
展開
\[f_i=f_j+A(i)^2+B(j)^2-2A(i)B(j) \]
移項
\[B(j)^2+f_j=2A(i)B(j)-A(i)^2+f_i \]
因為斜率優化與\(y,k,x\)有關,由於\(-A(i)^2+f_i\)是常數,所以可以省略
原式就變成了
\[B(j)^2+f_j=2A(i)B(j) \]
\(y=kx\)
\(B(j)^2+f_j\)
\(2A(i)\)為\(k\)
\(B(j)\)為\(x\)
因為\(2A(i)\)為正數,所以整個函式單調遞增,因此我們使用單調佇列維護下凸包。
#include <bits/stdc++.h> #define int long long using namespace std ; const int MAXN = 5e4 + 5 ; int n , L ; int f[ MAXN ] , q[ MAXN ] , l = 1 , r = 0 , sum[ MAXN ] ; inline int A ( int p ) { return p + sum[ p ] - 1 - L ; } inline int B ( int p ) { return sum[ p ] + p ; } inline int X ( int p ) { return B ( p ) ; } inline int Y ( int p ) { return f[ p ] + B ( p ) * B ( p ) ; } inline int K ( int p ) { return 2 * A ( p ) ; } inline int read () { int tot = 0 , f = 1 ; char c = getchar () ; while ( c < '0' || c > '9' ) { if ( c == '-' ) f = -1 ; c = getchar () ; } while ( c >= '0' && c <= '9' ) { tot = ( tot << 1 ) + ( tot << 3 ) + ( c ^ 48 ) ; c = getchar () ; } return tot * f ; } signed main () { n = read () ; L = read () ; for ( int i = 1 ; i <= n ; i ++ ) sum[ i ] = sum[ i - 1 ] + read () ; q[ ++ r ] = 0 ; for ( int i = 1 ; i <= n ; i ++ ) { while ( l < r && K ( i ) * ( X ( q[ l + 1 ] ) - X ( q[ l ] ) ) >= Y ( q[ l + 1 ] ) - Y ( q[ l ] ) ) l ++ ; // 彈出決策點 int p = q[ l ] ; f[ i ] = f[ p ] + ( A ( i ) - B ( p ) ) * ( A ( i ) - B ( p ) ) ; while ( l < r && ( ( Y ( i ) - Y ( q[ r ] ) ) * ( X ( q[ r ] ) - X ( q[ r - 1 ] ) ) <= ( X ( i ) - X ( q[ r ] ) ) * ( Y ( q[ r ] ) - Y ( q[ r - 1 ] ) ) ) ) r -- ; // 刪除不必要的決策點(上凸點) q[ ++ r ] = i ; } cout << f[ n ] << endl ; return 0 ; }