Print Article
Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 131072/65536 K (Java/Others)
Total Submission(s): 2224 Accepted Submission(s): 728
One day Zero want to print an article which has N words, and each word i has a cost Ci to be printed. Also, Zero know that print k words in one line will cost

M is a const number.
Now Zero want to know the minimum cost in order to arrange the article perfectly.
此題是很基礎的斜率DP的入門題。
題意很清楚,就是輸出序列a[n],每連續輸出的費用是連續輸出的數字和的平方加上常數M
讓我們求這個費用的最小值。
設dp[i]表示輸出前i個的最小費用,那么有如下的DP方程:
dp[i]= min{ dp[j]+(sum[i]-sum[j])^2 +M } 0<j<i
其中 sum[i]表示數字的前i項和。
相信都能理解上面的方程。
直接求解上面的方程的話復雜度是O(n^2)
對於500000的規模顯然是超時的。下面講解下如何用斜率優化DP使得復雜度降低一維。
我們首先假設在算 dp[i]時,k<j ,j點比k點優。
也就是
dp[j]+(sum[i]-sum[j])^2+M <= dp[k]+(sum[i]-sum[k])^2+M;
所謂j比k優就是DP方程里面的值更小
對上述方程進行整理很容易得到:
[(dp[j]+sum[j]*sum[j])-(dp[k]+sum[k]*sum[k])] / 2(sum[j]-sum[k]) <=sum[i].
注意整理中要考慮下正負,涉及到不等號的方向。
左邊我們發現如果令:yj=dp[j]+sum[j]*sum[j] xj=2*sum[j]
那么就變成了斜率表達式:(yj-yk)/(xj-xk) <= sum[i];
而且不等式右邊是遞增的。
所以我們可以看出以下兩點:我們令g[k,j]=(yj-yk)/(xj-xk)
第一:如果上面的不等式成立,那就說j比k優,而且隨着i的增大上述不等式一定是成立的,也就是對i以后算DP值時,j都比k優。那么k就是可以淘汰的。
第二:如果 k<j<i 而且 g[k,j]>g[j,i] 那么 j 是可以淘汰的。
假設 g[j,i]<sum[i]就是i比j優,那么j沒有存在的價值
相反如果 g[j,i]>sum[i] 那么同樣有 g[k,j]>sum[i] 那么 k比 j優 那么 j 是可以淘汰的
所以這樣相當於在維護一個下凸的圖形,斜率在逐漸增大。
通過一個隊列來維護。
/* HDU 3507 */ #include<stdio.h> #include<iostream> #include<string.h> #include<queue> using namespace std; const int MAXN=500010; int dp[MAXN]; int q[MAXN];//隊列 int sum[MAXN]; int head,tail,n,m; // dp[i]= min{ dp[j]+M+(sum[i]-sum[j])^2 }; int getDP(int i,int j) { return dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]); } int getUP(int j,int k) //yj-yk部分 { return dp[j]+sum[j]*sum[j]-(dp[k]+sum[k]*sum[k]); } int getDOWN(int j,int k) { return 2*(sum[j]-sum[k]); } int main() { // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); while(scanf("%d%d",&n,&m)==2) { for(int i=1;i<=n;i++) scanf("%d",&sum[i]); sum[0]=dp[0]=0; for(int i=1;i<=n;i++) sum[i]+=sum[i-1]; head=tail=0; q[tail++]=0; for(int i=1;i<=n;i++) { //把斜率轉成相乘,注意順序,否則不等號方向會改變的 while(head+1<tail && getUP(q[head+1],q[head])<=sum[i]*getDOWN(q[head+1],q[head])) head++; dp[i]=getDP(i,q[head]); while(head+1<tail && getUP(i,q[tail-1])*getDOWN(q[tail-1],q[tail-2])<=getUP(q[tail-1],q[tail-2])*getDOWN(i,q[tail-1])) tail--; q[tail++]=i; } printf("%d\n",dp[n]); } return 0; }
初學斜率DP感覺很難,但是理解了原理之后感覺還是比較簡單的。等深入之后再來總結下斜率DP