【洛谷P4983】忘情

题目

题目链接:https://www.luogu.com.cn/problem/P4983
你的 \(npy\) 为了恶心你,特地请了四位大神和一个辣鸡!
\(\rm hdxrie\) 说:“我们得求和。”于是有了 \(\sum\limits_{i=1}^{n}x_i\)
\(\rm Imagine\) 说:“我们得有平均数。”于是有了 \(\bar x\)
\(\rm TimeTraveller\) 说:“我们得有加减乘除。”于是有了一些恶心的组合。
\(\rm Althen·Way·Satan\) 说:“我们还得有平方。”于是我们将它平方。
最垃圾的 \(\rm ZredXNy\) 说:“那我帮你们整合一下。”
于是,我们得到了这么一个式子 \(:\)

\[\frac{\left((\sum\limits_{i=1}^{n}x_i×\bar x)+\bar x\right)^2}{\bar x^2} \]

我们定义一段序列的值为这个,其中 \(n\) 为此序列的元素个数。
我们给定一段长度为 \(n\) 的序列,现在要求将它分成 \(m\) 段,要求每一段的值的总和最小,求出这个最小值。
\(m\leq n\leq 10^5,x_i\leq 10^3\)

思路

其实那一坨式子就等于 \((\sum x_i+1)^2\)
我们记 \(g_i\) 表示分为 \(i\) 段后每一段和的最小值,假设这次合并的两段的区间和分别为 \(x,y\),那么有 \(g_i-g_{i-1}=(x+y+1)^2-(x+1)^2-(y+1)^2=2xy-1\)。由于我们每次选择的相邻两段长度乘积 \(xy\) 肯定不会降,所以 \(g_i-g_{i-1}\) 是单调不降的,所以 \(g_i\) 是一个下凸函数。
又因为题目要求恰好分为 \(m\) 段,考虑 wqs 二分。设 \(f_i\) 表示前 \(i\) 个数字分成若干段的最小代价,那么有

\[f_i=\min(f_j+(s_i-s_j+1)^2-mid) \]

其中 \(s\)\(x\) 的前缀和。
一眼就能看出这是一个斜率优化的板子题。直接用单调队列维护一下下凸壳即可。
时间复杂度 \(O(n\log V)\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=100010;
int n,m,a[N],q[N],g[N];
ll ans,f[N],X[N],Y[N];

double slope(int i,int j)
{
	return 1.0*(Y[i]-Y[j])/(X[i]-X[j]);
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		a[i]+=a[i-1];
	}
	ll l=0,r=1e18,mid;
	while (l<=r)
	{
		mid=(l+r)>>1;
		int hd=1,tl=1;
		for (int i=1;i<=n;i++)
		{
			while (tl>hd && slope(q[hd],q[hd+1])<a[i]) hd++;
			f[i]=f[q[hd]]+1LL*(a[i]-a[q[hd]]+1)*(a[i]-a[q[hd]]+1)+mid;
			g[i]=g[q[hd]]+1;
			X[i]=2LL*a[i]; Y[i]=f[i]+1LL*a[i]*a[i]-2LL*a[i];
			while (tl>hd && slope(q[tl],q[tl-1])>slope(i,q[tl])) tl--;
			q[++tl]=i;
		}
		if (g[n]<=m) ans=f[n]-mid*m,r=mid-1;
			else l=mid+1;
	}
	cout<<ans;
	return 0;
}
posted @ 2021-07-14 19:11  stoorz  阅读(78)  评论(0)    收藏  举报