洛谷 P3648 [APIO2014] 序列分割(二维斜率优化)

虽然这是二维斜率优化(听起来很高级的样子),但其实和一维的差不多……

题目传送门

解题思路

设 f(i,j) 表示前 i 个数分成 j 个序列的最大总得分。

聪明的你也许可以很快想出这个转移方程:

f(i,j)=\max( f(k,j-1) + S_k \times (S_i-S_k) )

其中 0\leq k<iS_i 为序列前 i 个数的前缀和。

这样的作法时间复杂度是 O(n^2) 的,不能通过本题,所以考虑斜率优化。

斜率优化

这是个二维的斜率优化。

先将上述转移方程展开,得:

f(i,j)=f(k,j-1)+S_i\times S_k -S_k^2

将 k 看成未知数,将 i 和 j 看成常数。于是,我们边将含 k 项移到等号左边,其余放在等号右边。

于是,式子就变成了:

-f(k,j-1)+S_k^2=-f(i,j)+S_i\times S_k

与 y=kx+b 相对比,可以得出:

y=-f(k,j-1)+S_k^2

k=S_i

x=S_k

b=-f(i,j)

通过分析,我们发现:

(我们还是比较幸运的)

k 和 x 都是递增的,于是只是一个普通的斜率优化,不需要二分或 CDQ 分治。

现在来考虑一下,应该是维护上凸包,还是下凸包?

虽然这里是取最大值,但是你得注意一下,我们的截距 b=-f(i,j)

如果维护上凸包,那么就会使截距尽量大,那 f(i,j) 就会很小。

所以我们要维护下凸包。

代码

二维的斜率优化,在一维的基础上多加一层循环就行了。

#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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值