斜率dp

HUD-3507
Zero has an old printer that doesn't work well sometimes. As it is antique, he still like to use it to print articles. But it is too old to work for a long time and it will certainly wear and tear, so Zero use a cost to evaluate this degree.
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

[(sum_{i=1}^kC_i)^2+M ]

M is a const number.
Now Zero want to know the minimum cost in order to arrange the article perfectly.

Input
There are many test cases. For each test case, There are two numbers N and M in the first line (0 ≤ n ≤ 500000, 0 ≤ M ≤ 1000). Then, there are N numbers in the next 2 to N + 1 lines. Input are terminated by EOF.

Output
A single number, meaning the mininum cost to print the article.

Sample Input

5 5
5
9
5
7
5

Sample Output

230

很容易推出:(dp[i]=min(dp[j]+(s[i]-s[j])^2+M)) 其中(j<i)
这样的不加优化的裸dp是(O(n^2))的,5e5的数据肯定爆了
假设(k<j<i)
如果对于dp[i], j 比 k 优,有
(dp[j]+(s[i]-s[j])^2+M)<dp[k]+(s[i]-s[k])^2+M))

(dp[j]+s[j]^2-(dp[k]+s[j]^2)<2s[i](s[j]-s[k]))

设$$g(k,j)=frac{dp[j]+s[j]2-(dp[k]+s[j]2)}{2(s[j]-s[k])}<s[i]$$
也就是$$frac{y2-y1}{x2-x1}<k$$
如果(g(k,j)<s[i]),说明j比k优,且s[i]是递增的,j永远比k优
如果(g(a,b)>g(b,c)) b永远不是较优的:

  1. (g(b,c)<=s[i]),c比b优或一样
  2. (g(b,c)>s[i]),b比c优但是a比b优,所以a优

所以,可以用队列维护一个下凸包,为何不是栈呢,这点想了好久,如果j比k优,j永远比k优,所以没用的就扔了。

#include<bits/stdc++.h>
#include<iostream>
using namespace std;
template<class T> inline bool read(T &x){
    x=0;register char c=getchar();register bool f=0;
    while(!isdigit(c)){if(c==EOF)return false;f^=c=='-',c=getchar();}
    while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
    if(f)x=-x;
    return true;
}
typedef long long ll;
const ll MAXN=5e5+8,inf=0x3f3f3f3f,mod=1e9+7;
int n,m;
int s[MAXN],dp[MAXN],cnt;
int que[MAXN],l,r;//队列  [l,r)
inline int dy(int a,int b){return dp[a]+s[a]*s[a]-dp[b]-s[b]*s[b];}
inline int dx(int a,int b){return 2*(s[a]-s[b]);}//a>b,如果不是这样,返回负数,不等式要变号
inline void up(int i,int j){dp[i]=dp[j]+(s[i]-s[j])*(s[i]-s[j])+m;}//用j更新i
int main() {
    while(read(n)&&read(m)){
        for(int i=1;i<=n;++i){
            read(s[i]);
            s[i]+=s[i-1];
        }
        l=r=0;
        que[r++]=0;
        for(int i=1;i<=n;++i){
            while(l<r-1&&dy(que[l+1],que[l])<=s[i]*dx(que[l+1],que[l]))l++;//直接l++,而不是零时变量
            up(i,que[l]);
            while(l<r-1&&dy(que[r-1],que[r-2])*dx(i,que[r-1])>=dy(i,que[r-1])*dx(que[r-1],que[r-2]))r--;
            que[r++]=i;
        }printf("%d
",dp[n]);
    }
    return 0;
}

每个i入队一次,出队最多一次,所以是O(n)

原文地址:https://www.cnblogs.com/foursmonth/p/14155949.html