题意
打印一篇长度为n的文章,求最小代价
其中代价的计算公式为:一行元素权值加和的平方加上换行的代价,即下图公式:
思路
很容易想到用dp
状态转移也好写
dp[i]=min(dp[j]+(sum[i]-sum[j])^2+m);
然后随便的算一下复杂度 O(n^2)
再看一下数据范围 (0 ≤ n ≤ 500000, 0 ≤ M ≤ 1000)
GG……..
所幸我们可以优化,
dp[i]=min(dp[j]+(sum[i]-sum[j])^2+m);
dp[i]=min(dp[k]+(sum[i]-sum[k])^2+m);
我们假设 j比k优秀
即
化简可得:
然后令
然后令
化简为
一不小心发现满足斜率优化dp
然后模板走一波 …
不知道斜率dp? 看这里
然后分析一下:
不等式右边是递增的。
令g[k,j]=(yj-yk)/(xj-xk)
可以看出以下两点:
1.如果上面的不等式成立,那就说j比k优,而且随着i的增大上述不等式一定是成立的,也就是对i以后算dp值时,j都比k优。那么k就是可以淘汰的。
2.如果 而且 g[k,j]>g[j,i] 那么 j 是可以淘汰的。
假设 就是i比j优,那么j没有存在的价值
相反如果 g[j,i]>sum[i] 那么同样有 g[k,j]>sum[i] 那么 k比 j优 那么 j 是可以淘汰的
所以这样相当于在维护一个下凸的图形,斜率在逐渐增大。
然后用单调队列就好了
参考代码
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
long long an[500500];
long long sum[500500];
long long n,m;
long long dp[500500];
long long q[500500];
long long front=1,rear=0;
long long zi(long long a,long long b){
long long ans=dp[a]-dp[b]+ (sum[a]+sum[b])*(sum[a]-sum[b]);
return ans;
}
long long mu(long long a,long long b){
return 2*(sum[a]-sum[b]);
}
void calcul(long long i){
while(front+1<rear&&zi(q[front+1],q[front])<=sum[i]*mu(q[front+1],q[front])){ //单调队列的出队
front++;
}
dp[i]=dp[q[front]]+(sum[i]-sum[q[front]])*(sum[i]-sum[q[front]])+m;//状态转移方程
//维护单调队列(斜率递增)
while(front+1<rear&& zi(q[rear-1],q[rear-2])*mu(i,q[rear-1])>=zi(i,q[rear-1])*mu(q[rear-1],q[rear-2])){
rear--;
}
q[rear++]=i;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
while(~scanf("%lld%lld",&n,&m)){
front=1,rear=1;
sum[0]=0;
for(int i=1;i<=n;i++){
scanf("%lld",&an[i]);
sum[i]=sum[i-1]+an[i];
}
q[rear++]=0;
dp[0]=0;
for(long long i=1;i<=n;i++){
calcul(i);
}
printf("%lld\n",dp[n]);
}
return 0;
}