原題連結
很好的題,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;
}