- 给定一个长度为\(n\)的序列\(a\),要求你把它划分成\(m\)段。
- 令\(sum_i\)表示第\(i\)段的数之和,求\(\sum_{i=1}^m(sum_i+1)^2\)的最小值。
- \(m\le n\le10^5\)
\(WQS\)二分
一来这种套路早就见过了,二来事先知道了这题是\(WQS\)二分,于是这题就变得非常水。。。
不过感觉如果像我第一次做这种题那样没想到\(WQS\)二分的话,还是比较棘手的。
考虑\((a+b+1)^2-((a+1)^2+(b+1)^2)=2ab-1>0\),因此划分成更多段一定会更优。
因此我们二分一个额外代价\(C\),然后每次转移\(f\)的时候附加上一个\(C\),并记录\(g\)表示最优解划分的段数。
那么只要找到一个合适的\(C\)使得\(g_n\)恰好等于\(m\)就可以了。
斜率优化\(DP\)
考虑转移方程:(\(s\)表示\(a\)的前缀和)
\[f_i=f_j+(s_i-s_j+1)^2+C \]把右边的项拆开并且只保留和\(j\)有关的项得到:
\[f_j+s_j^2-2s_j-2s_i\times s_j \]所以一个转移点\(j\)优于\(k\)(\(j>k\))的充要条件就是:
\[f_j+s_j^2-2s_j-2s_i\times s_j<f_k+s_k^2-2s_k-2s_i\times s_k\\ (f_j+s_j^2-2s_j)-(f_k+s_k^2-2s_k)<2s_i\times(s_j-s_k) \]由于\(s_j-s_k\)显然为正,因此就有:
\[s_i>\frac{(f_j+s_j^2-2s_j)-(f_k+s_k^2-2s_k)}{2(s_j-s_k)} \]那么我们只要维护一个单调队列,然后就可以轻松斜率优化了。
代码:\(O(nlogV)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LL long long
using namespace std;
int n,m,a[N+5];
LL f[N+5];int g[N+5],q[N+5];I bool Check(Con LL& C)//斜率优化DP验证
{
RI i,H,T;for(q[H=T=1]=0,i=1;i<=n;++i)
{
#define A(x) (f[x]+1LL*a[x]*a[x]-2*a[x])
#define S(x,y) 0.5*(A(y)-A(x))/(a[y]-a[x])
W(H^T&&S(q[H],q[H+1])<a[i]) ++H;//弹出队首的不优元素
f[i]=f[q[H]]+1LL*(a[i]-a[q[H]]+1)*(a[i]-a[q[H]]+1)+C,g[i]=g[q[H]]+1;//转移,注意加上额外代价
W(H^T&&S(q[T-1],q[T])>S(q[T],i)) --T;q[++T]=i;//维护斜率单调
}return g[n]<=m;
}
int main()
{
RI i;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",a+i),a[i]+=a[i-1];
LL l=0,r=1e18,mid;W(l^r) Check(mid=l+r>>1)?r=mid:l=mid+1;//WQS二分
return Check(l),printf("%lld\n",f[n]-r*m),0;
}