P4983 忘情 题解

首先可以发现 dp 方程为:\(dp_{i,k}=dp_{j,k-1}+(sum_i-sum_j+1)^2\)

那么展开即为:\(dp_{j,k}+sum_j^2-sum_j=2\times sum_i\times sum_j+dp_{i,k}-sum_i^2-1-sum_i\)

很明显斜率 dp,\(2\times sum_i,sum_j\) 均单调递增,用单调队列维护即可。

时间复杂度:\(O(n\times m)\)

考虑优化。

可证以选取次数为 \(x\) 轴,选取此次数的最大值为 \(y\) 轴所得到的形状为下凸包,因为一个值每次分开成两个值 \(x,y\) 答案都会减小 \(2\times x\times y-1\) 的答案,切的越少,\(x,y\) 的值就会越大,\(2\times x\times y-1\) 的值就会越大,所以呈下凸包。

所以我们要找到一个正好能切到横坐标为 \(m\) 对应的点的斜率。

那么我们先二分斜率,然后判断这个斜率交在哪个点上,如果交的点小了,斜率变小,否则斜率变大,直到交到 \(m\) 点为止。

现在问题是我们如何知道它交在哪一个点,这里讲一个比较好理解的方法(虽然没有严格证明,严格证明的不好理解,可以移步其他题解)。

实际上对于每一次求斜率,我们可以对在转移时加上斜率,这样所有的点的 \(y\) 坐标会发生改变,导致转移方式不同,最后一个数的选取个数即是那个与 \(x\) 轴对应的点相交。

而由于最后的转移方式改变,所以得对 \(ans\) 减去 \(m\times k\)

时间复杂度:\(O(n\times\log(n))\)

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int N=1e5+5;
int n,sum[N],m,f[N],g[N],q[N];
int clac(int x)
{
	return x*x;
}
double slope(int x,int y)
{
	return (f[x]+clac(sum[x])-2*sum[x]-(f[y]+clac(sum[y])-2*sum[y]))/1.0/(sum[x]-sum[y]);
}
bool check(int x)
{
	f[0]=0;
	int l=1,r=1;
	for(int i=1;i<=n;i++)
	{
		while(l<r&&slope(q[l],q[l+1])<2*sum[i])l++;
		f[i]=f[q[l]]+x+clac(1+sum[i]-sum[q[l]]);
		g[i]=g[q[l]]+1;
		while(l<r&&slope(q[r],q[r-1])>slope(q[r],i))r--;
		q[++r]=i;
	}
	return g[n]>m;
}
signed main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)scanf("%lld",&sum[i]),sum[i]+=sum[i-1];
	int l=1,r=1e18;
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(check(mid))l=mid+1;
		else r=mid;
	}
	check(l);
	printf("%lld",f[n]-l*m);
	return 0;
}
posted @ 2023-02-24 13:42  Gmt丶Fu9ture  阅读(33)  评论(0)    收藏  举报