背景:
我连这都不会…
题目传送门:
(这是一道例题)http://acm.hdu.edu.cn/showproblem.php?pid=3507
题意:
给定n,m,接下来有n个数ai,表示每一个单词的权值。现在你需要将这些单词分行,每一行的权值的计算公式是:(∑i=1kai)2+m,现在求最小的权值和。
思路:
显然我们能想到常规做法。
设fi表示从第1个单词到第i个单词的权值和的最小值。
设sumk=∑i=1kai
显然得到转移方程:fi=minj=1i{fj+(sumi−sumj)2}。
时间复杂度:Θ(n2)。
可是我们过不了怎么办?
考虑i从j,k转移过来的情况,且从j转移更优,j>k。
可以得到不等式:
fj+(sumi−sumj)2<fk+(sumi−sumk)2
展开括号,得:
fj+sumi2+sumj2−2sumisumj<fk+sumi2+sumk2−2sumisumk
消去同类项,得:
fj+sumj2−2sumisumj<fk+sumk2−2sumisumk
再移项,得:
fj−fk+sumj2−sumk2<+2sumisumj−2sumisumk
提取同类项,得:
fj−fk+sumj2−sumk2<2sumi(sumj−sumk)
再移项,得:
sumj−sumkfj−fk+sumj2−sumk2<2sumi+1
设Ti=fi+sumi2,得:
sumj−sumkTj−Tk<2sumi+1
结论:也就是说若存在j>k且sumj−sumkTj−Tk<2sumi,那么从j转移更优。
ps:联想一下数学课本中的k=x2−x1y2−y1,发现左边的式子好像斜率啊。斜率优化就是这么来的。
那么有什么用呢?

因为T是由f和sum得到的,sum是固定的,所以我们求完了f,就可以得到T。
如图,从我们的斜率推导中我们可以以sum作为x轴,T作为y轴建立坐标系,对应的点代表f的值。
我们在求解fnow时,假设可选i,j,k三个点转移,且如上图所示。
由于kkj>kji,所以sumj−sumkTj−Tk>sumi−sumjTi−Tj
由上面的结论可知两边的式子要与2sumnow比较。
就存在三种可能:
[1]:
sumj−sumkTj−Tk>sumi−sumjTi−Tj>2sumnow
由上面的结论可知此时j比i优,但k比j优,所以选择k转移。
[2]:
sumj−sumkTj−Tk>2sumnow>sumi−sumjTi−Tj
由上面的结论可知此时k比j优,i比j优,所以选择i或k转移。
[3]:
2sumnow>sumj−sumkTj−Tk>sumi−sumjTi−Tj
由上面的结论可知此时i比j优,j比k优,所以选择i转移。
综上所述,我们一定不会选择从j转移。
我们维护一个这样的类似凸包且斜率单调递增的东西:

假设kji>2sumnow且kkj<2sumnow,我们可以从上面的结论得出j点比所有比k小的点都优,比所有比i大的也优。所以我们二分查找斜率比2sumnow小的编号最大的点,就是最优的转移点。由于sum也有单调性,我们直接维护一个单调队列就可以了。
维护时,若队列前面的点不满足结论,则删掉;若后面的点不满足斜率单调递增,则删掉。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
int n,m;
LL a[1000010],sum[1000010],f[1000010];
int que[1000010];
LL calc1(int x,int y)
{
return sum[y]-sum[x];
}
LL calc2(int x,int y)
{
return (f[y]+sum[y]*sum[y])-(f[x]+sum[x]*sum[x]);
}
int main()
{
while(scanf("%d %d",&n,&m)!=EOF)
{
f[0]=sum[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
}
int head=1,tail=1;
que[1]=0;
for(int i=1;i<=n;i++)
{
while(head<tail&&calc2(que[head],que[head+1])<=(LL)2*sum[i]*calc1(que[head],que[head+1])) head++;
f[i]=f[que[head]]+calc1(que[head],i)*calc1(que[head],i)+m;
while(head<tail&&calc2(que[tail],i)*calc1(que[tail-1],que[tail])<=calc2(que[tail-1],que[tail])*calc1(que[tail],i)) tail--;
que[++tail]=i;
}
printf("%lld\n",f[n]);
}
}