P4983 忘情

summ1t發表於2024-11-05

原題連結

很好的題,wqs 二分+斜率最佳化 dp。只可惜是李超線段樹玩家最傷心的一集

對原式先化簡:

\(\begin{aligned} \frac{((\sum_{i=1}^{n}x_i\times \overline{x})+\overline{x})^2}{\overline{x}^2}&=\frac{(n\overline{x}^2+\overline{x})^2}{\overline{x}^2} \\&=\frac{(n\overline{x}+1)^2\times \overline{x}^2}{\overline{x^2}} \\&=(n\overline{x}+1)^2 \\&=(\sum_{i=1}^{n}x_i+1)^2 \end{aligned}\)

所以一段的值即為段內和 \(+1\) 的平方,答案即為所有劃分段的值總和。

令橫座標表示所分段數,縱座標表示答案,很容易發現答案具有凸性。

所以上 wqs 二分,接下來不考慮段數限制,考慮怎麼做?

\(f_i\) 表示前 \(i\) 個數得到的最小价值,\(k\) 表示二分斜率,\(s\) 表示字首和,轉移方程不難得到:\(f_i=f_j+(s_i-s_j+1)^2-k\)

這個式子太典了,斜率最佳化一下就做完了。

#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define ll long long
#define ld long double
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x*f;
}
const int N=1e5+10;
const ll INF=1e18;
int n,m,a[N],g[N],q[N];ll f[N],s[N];
ll Y(int a){return f[a]+s[a]*s[a]-2*s[a];}
ll X(int a){return s[a];}
ld slope(int a,int b){return (ld)(Y(a)-Y(b))/(X(a)-X(b));}
int calc(ll mid){
	int hh=1,tt=1;q[1]=0;
	memset(g,0,sizeof(0));
	FOR(i,1,n){
		while(hh<tt&&slope(q[hh],q[hh+1])<2*s[i]) hh++;
		f[i]=f[q[hh]]+(s[i]-s[q[hh]]+1)*(s[i]-s[q[hh]]+1)+mid;
		g[i]=g[q[hh]]+1;
		while(hh<tt&&slope(q[tt],q[tt-1])>slope(q[tt-1],i)) tt--;
		q[++tt]=i;
	}
	return g[n];
}
int main(){
	n=rd,m=rd;
	FOR(i,1,n) a[i]=rd,s[i]=s[i-1]+a[i];
	ll l=0,r=INF,ans=0;
	while(l<=r){
		ll mid=(l+r)>>1ll;
		if(calc(mid)<=m) r=mid-1,ans=mid;
		else l=mid+1;
	}
	calc(ans);
	printf("%lld\n",f[n]-m*ans);
	return 0;
}