虽然这是二维斜率优化(听起来很高级的样子),但其实和一维的差不多……
解题思路
设 表示前 个数分成 个序列的最大总得分。
聪明的你也许可以很快想出这个转移方程:
其中 , 为序列前 个数的前缀和。
这样的作法时间复杂度是 的,不能通过本题,所以考虑斜率优化。
斜率优化
这是个二维的斜率优化。
先将上述转移方程展开,得:
将 看成未知数,将 和 看成常数。于是,我们边将含 项移到等号左边,其余放在等号右边。
于是,式子就变成了:
与 相对比,可以得出:
通过分析,我们发现:
(我们还是比较幸运的)
和 都是递增的,于是只是一个普通的斜率优化,不需要二分或 CDQ 分治。
现在来考虑一下,应该是维护上凸包,还是下凸包?
虽然这里是取最大值,但是你得注意一下,我们的截距 。
如果维护上凸包,那么就会使截距尽量大,那 就会很小。
所以我们要维护下凸包。
代码
二维的斜率优化,在一维的基础上多加一层循环就行了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int a[100001],sum[100001];
int f[100001][201];
int k;
int q[100001];
int fa[100001][201];
int dx(int i,int j)
{
int x1=sum[i];
int x2=sum[j];
return x1-x2;
}
int dy(int i,int j)
{
int y1=sum[i]*sum[i]-f[i][k-1];
int y2=sum[j]*sum[j]-f[j][k-1];
return y1-y2;
}
void print(int x,int y)
{
if(x<=0)return;
print(fa[x][y],y-1);
if(x!=n)
cout<<x<<" ";
}
main()
{
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
int head,tail;
for(k=1;k<=m;k++)
{
head=0,tail=1;
for(int i=1;i<=n;i++)
{
while(head<tail&&dy(q[tail],q[tail-1])*dx(i-1,q[tail-1])>=dy(i-1,q[tail-1])*dx(q[tail],q[tail-1]))
tail--;
q[++tail]=i-1;
while(head<tail&&dy(q[head+1],q[head])<=sum[i]*dx(q[head+1],q[head]))
head++;
f[i][k]=f[q[head]][k-1]+sum[q[head]]*(sum[i]-sum[q[head]]);
fa[i][k]=q[head];
}
}
cout<<f[n][m]<<endl;
print(n,m);
}