Problem Description
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个数,常数为m,将这n个数分组,每一组的花费是这一组内所有数的和的平方再加上常数m,问分组的最小花费
分析:
只求最终结果而不要求如何分组,显然可以联系到DP,那么就可以写出最朴素的DP方程:
这是个的DP,显然n这个5e5的范围会超时,然后就想到要斜率优化
设 ,且
显然此时j优于k
乘开移项合并同类项后可以得到
令
那么上面的式子就可以化成
再令
所以若满足 就表示j优于k
得到这样一个斜率表达式后就可以进行斜率优化了:
设
如果 那么i优于j优于k,i为答案
如果 那么k优于j优于i,k为答案
这两种情况下j都不会是答案,那么就不需要进行答案的转移判断,这就是一种优化
即就是:按照斜率()的递增维护一个下凸包 就可以在
实现
做法总结如下:
1、核心:用一个单调队列维护解集;
2、求解:从队头开始,如果已有元素a b c,当i点要求解时,如果,那么说明b点比a点更优,a点可以排除,于是a出队。最后
;
3、维护:假设队列中从头到尾已经有元素a b c。那么当d要入队的时候,我们维护队列的下凸性质,即如果,那么就将c点删除。直到找到
为止,并将d点加入在该位置中。
参考博客:Accept的博客
【但我觉得这篇博客里队列维护的单调性那里好像有一点点问题嗷qwq】
【我还看到12年kuangbin的评论qwq】
一点问题:维护凸包那里如果 当前的点和队尾的斜率 与 队尾两点的斜率 相等的话,我觉得放不放进队列里都没有影响的吧
然后我写完这句话就发现其实是不对的qwq
于是我又去问了11-大佬嘤嘤嘤
假如现在队列里已经有了a b c,现在要看d能不能入,这时候
这种时候d就要入队列然后弹出c,
如果d进但不弹出c 那么更新head的时候就会丢失b的信息
如果d不进也不弹出c 那么继续向后更新凸包时就丢失了d的信息
所以这种情况的正确理解是,虽然当前都是等效的,但是还是要继续向后更新,所以要入d弹出c
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+7;
int dp[maxn],sum[maxn],q[maxn];
//dp[i]=min{dp[j]+M+(sum[i+1]-sum[j])^2};
int head,tail,n,m;
int getdp(int i,int j) {return dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]);}
int getup(int j,int k) {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()
{
while (~scanf("%d%d",&n,&m))
{
for (int i=1;i<=n;i++) scanf("%d",&sum[i]);
sum[0]=0;
for (int i=1;i<=n;i++) sum[i]+=sum[i-1];
head=tail=0;
q[tail++]=0;
dp[0]=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;
}