打印文章
一、题意及数据范围
题目描述
题目大意:输出N个数字a[N],输出的时候可以连续的输出,每连续输出一串,它的费用是 “这串数字和的平方加上一个常数M”,求最小的费用。
数据范围
n<=500000 , m<=1000。
二、解法
基本思路
根据题目,我们可以列出dp[i]=dp[j]+(a[i]-a[j])2+m,其中dp[i]表示i点是最后一段的最后一个数字的最小花费,a[j]是前缀和。但是这个dp式显然是O(n2)的,我们考虑怎么对它进行优化,化简得:
dp[i]=dp[j]+a[i]2+a[j]2-2*a[i]*a[j]+m
由于这个dp式在计算时需要a[i]和a[j]的乘积,单调队列就不能维护了。
斜率优化
半年多没接触,都不知道怎么用它了……
接下来有一些玄学推导:
设用j更新比k更优,则:
dp[j]+a[i]2+a[j]2-2*a[i]*a[j]+m<dp[k]+a[i]2+a[k]2-2*a[i]*a[k]
消去同类项:dp[j]+a[j]2-2*a[i]*a[j]<dp[k]+a[k]2-2*a[i]*a[k]
移项:dp[j]-dp[k]+a[j]2-a[k]2<2*a[i]*(a[j]-a[k])
我们设f[j]=dp[j]+a[j]2,f[k]=dp[k]+a[k]2
dp[j]−a[k]a[j]−a[k]\frac{dp[j]-a[k]}{a[j]-a[k]}a[j]−a[k]dp[j]−a[k]<2*a[i]
这个东西……是不是很像斜率
哈!这就是它为什么要叫斜率优化。
具体实现
其实最重要的东西我都讲了,我们可以用这个式子结合单调队列维护凸包,就能过了。
看代码吧。
#include <cstdio>
#define LL long long
const LL MAXN = 500005;
LL read()
{
LL x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*flag;
}
LL n,m,a[MAXN],q[MAXN],dp[MAXN];
LL slope_up(LL j,LL k)
{
return (dp[j]+a[j]*a[j])-(dp[k]+a[k]*a[k]);
}
LL slope_down(LL j,LL k)
{
return 2*a[j]-2*a[k];
}
int main()
{
while(~scanf("%lld %lld",&n,&m))
{
for(LL i=1;i<=n;i++)
a[i]=a[i-1]+read();
LL head=1,tail=0;
q[++tail]=0;
dp[0]=0;
for(LL i=1;i<=n;i++)
{
while(head<tail && slope_up(q[head+1],q[head])<=a[i]*slope_down(q[head+1],q[head])) head++;
//维护队首,如上述,当小于时就一定不比q[head+1]优。避免精度问题,使用乘法。
dp[i]=dp[q[head]]+(a[i]-a[q[head]])*(a[i]-a[q[head]])+m;
while(head<tail && slope_up(q[tail],q[tail-1])*slope_down(i,q[tail])>=slope_up(i,q[tail])*slope_down(q[tail],q[tail-1])) tail--;
//同样维护凸包,使用乘法。
q[++tail]=i;
}
printf("%lld\n",dp[n]);
}
}
注:本篇题解只是斜率优化入门,没有深究,后面做到难题时再补几发博客吧