HDU - 3507 , Print Article [ 斜率優化dp , 良心部落格 ]
題目標題:列印文章
給出N個單詞,每個單詞有個非負權值Ci,現在要將它們分成連續的若干段,每段的代價為此段單詞的權值和的平方,還要加一個常數M,即。現在想求出一種最優方案,使得總費用之和最小。
輸入格式
包含多組測試資料,對於每組測試資料。 第一行包含兩個整數N和M(0<=N<=500000,0<=M<=1000)。 第2-N+1行為N個整數。
輸出格式
輸出僅一個整數,表示最小的價值。
樣例輸入
5 5
5
9
5
7
5
3 0
1
2
3
樣例輸出
230
14
思路 :
其實看到這個題就是dp 而且遞推方程式為
dp[i] = min { dp[i] ,dp[j] + (sum[i] - sum[j])*(sum[i] - sum[j]) + m }
但是複雜度一估計直接爆炸,明顯用這個方程式寫出來的是一個二維的 dp 因此需要優化
如果對於 k 來說有一個更加優化的 j 的話 那麼 可以得到
dp[j] + ( sum[i] - sum[j] ) ^ 2 + m > dp[k] + (sum[i] - sum[k] ) ^ 2 + m ;
移項化簡可得
(dp[j] - dp[k]) + (sum[j] ^ 2 - sum[k] ^ 2) < 2 * (sum[j] - sum[k]) * sum[i];
令 up = (dp[j] - dp[k]) + (sum[j] ^ 2 - sum[k] ^ 2) , down = 2 * (sum[j] - sum[k])
那麼 sum[i] = up (j ,k ) / down(j ,k ) ;
sum[i] 是字首和因此sum陣列是遞增的,因此這個題可以用斜率優化
先看程式碼 :
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 5e5+50;
typedef long long ll;
ll dp[maxn] ,sum[maxn] ,v[maxn] ;
int n ,m ;
int que[maxn] ,head ,tail ;
ll getdp(int i ,int j ) { // dp
return dp[ j] + (sum[i] - sum[j]) * (sum[i] - sum[j]) + m;
}
ll getup(int j ,int k ) { // up
return dp[j] - dp[k] + sum[j] * sum[j] - sum[k] * sum[k] ;
}
ll getdown(int j ,int k ) { // down
return 2 * ( sum[j] - sum[k] );
}
int main() {
while( ~scanf("%d %d" , &n ,&m ) ) {
memset(dp ,0 ,sizeof(dp));
for (int i = 1 ; i <= n ; i ++ ) {
scanf ("%lld",&v[i]);
sum[i] = sum[i-1] + v[i];
}
head = tail = 0; dp[tail ++] = 0; // 不可以從 1 開始進入佇列,因為字首和是從 0 開始的
for (int i = 1 ; i <= n ; i ++ ) {
while(head + 1 < tail && getup(que[head+1] ,que[head]) < getdown(que[head+1] ,que[head]) * sum[i] ) head ++ ;
dp[i] = getdp(i ,que[head] ) ;
while(head + 1 < tail && getup(que[tail-1] ,que[tail-2]) * getdown(i ,que[tail-1]) >= getup(i ,que[tail-1]) * getdown(que[tail-1] ,que[tail-2])) tail --;
que[tail ++] = i;
}
printf("%lld\n",dp[n]);
}
return 0;
}
1 ) , 我們來看for迴圈裡面的第一個 while 就相當於在que[head] 後面還有 比 que[head] 更加優化情況因此每次都要 head ++ ,屬於這種情況 :
此時 i 為我們待求的 ,k 為que[ head ] , j 為 que[head + 1] 明顯 j要比 k更加優化
2 ) , 對於第二個while 迴圈 可以這樣理解 當前的 i 如果要加入佇列的話必須保持佇列的斜率是單調遞增的
這種情況就是 i 是當前待加入的 , j 代表 que[tail-1] , k 代表que[tail-2] , 這種情況下 j 是明顯不符合條件的因此要捨去 j , tail –