题目:
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
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
题目大意:
给你一个长度为n的序列,请你将这个序列分段,每一段序列的价值为,,最终使得这些段的价值和最小,输出这个最小值。
解题思路:
dp[i] = dp[j]+(sum[i]-sum[j])^2+M (i>j)
按照这样的转移方程,就能求得到答案,但是这样转移的话时间复杂度为(n^2),显然是要超时的。
所以我们要降下一维。
我们设当a < b<i时, dp[i]从dp[b]转移过来会优于从dp[a]转移过来。
那么我们可以得到
化简可得
也就是说,如果a,b两点的斜率满足这个不等式,那么从b点转移就优于a点,并且由于当k>i时,2*sum[k]>=2*sum[i],即sum[]函数是不减的,那么,在i之后b点就永远优于a点,那么在从i之后a点对于这个问题的求解就已经没有意义了。
我们维护一个队列,在这个队列中,对于每一个新的点i,将队列头中,与下一个点构成斜率小于等于2*sum[i]的点不要了,因为这些点在以后的转移中都是不如它的下一个点的。
而当我们要将当前元素放进队列尾的时候,考虑这样一个问题
我们要加入i点,如果i点和队列尾部的点构成这样上凸的形状的话,我们称尾部的点同样是没有意义的
如果 tail-1与tail构成的斜率大于2*sum[k],那么tail-1比tail优,
如果 tail-1与tail构成的斜率小于2*sum[k],那么tail比tail-1优,但是同样会有 tail与i构成的斜率小于2*sum[k],那么i比tail优
所以tail是没有意义的,舍去。
这样,在这个队列里的两点间斜率,后面的斜率就一定会比前面的大
那么 队列中所以点与之前点构成的斜率都会>=队列首和队列第二个元素点构成的斜率>=2*sum[i],那从队列首的那个元素转移过来岂不就是最优解了!
#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<cmath>
using namespace std;
const long long inf = 0x3f3f3f3f;
const int maxn = 500005;
int n;
long long m;
long long num[maxn];
long long sum[maxn],dp[maxn];
int q[maxn];
long long getup(int b, int a)
{
return dp[b]+sum[b]*sum[b]-dp[a]-sum[a]*sum[a];
}
long long getdown(int b, int a)
{
return sum[b]-sum[a];
}
int main()
{
while(scanf("%d%lld", &n, &m)!=EOF)
{
for(int i = 1; i <= n; i++)
{
scanf("%lld", &num[i]);
sum[i] = sum[i-1]+num[i];
}
int head = 1,tail = 1;
q[1] = 0;
for(int i = 1; i <= n; i++)
{
while(head<tail&&getup(q[head+1],q[head])<=2*sum[i]*getdown(q[head+1],q[head])) head++;
dp[i] = dp[q[head]]+(sum[i]-sum[q[head]])*(sum[i]-sum[q[head]])+m;
while(head<tail&&getup(q[tail],q[tail-1])*getdown(i,q[tail])>=getup(i,q[tail])*getdown(q[tail],q[tail-1])) tail--;
q[++tail] = i;
}
printf("%lld\n", dp[n]);
}
return 0;
}