1. 程式人生 > >HDU - 3507 , Print Article [ 斜率優化dp , 良心部落格 ]

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 –