[SDOI2016]征途
给定长度为 (n) 的序列 (a{n}),将其分为连续 (m) 段,和分别为 (v{m})。(v{m}) 的方差为 (E),求 (left(m^2 imes E ight)_{min})。
(1le nle 3000),(1le sum a_ile 30000)。
复习斜率优化第一题,遇到好多麻烦,写一篇题解记录。
首先推式探索 (m^2 imes E) 的本质:
设 (sum=sum a_i=sum v_i)。
[egin{split}
m^2 imes E=&m^2 imes frac{sumlimits_{i=1}^mleft(v_i-frac{sum}{m}
ight)^2}{m}\
=&m sumlimits_{i=1}^mleft(v_i-frac{sum}{m}
ight)^2\
=&m left(sumlimits_{i=1}^mv_i^2-sumlimits_{i=1}^m2v_icdotfrac{sum}{m}+sumlimits_{i=1}^mfrac{sum^2}{m^2}
ight)\
=&m left(sumlimits_{i=1}^mv_i^2-2sumcdotfrac{sum}{m}+mcdotfrac{sum^2}{m^2}
ight)\
=&m left(sumlimits_{i=1}^mv_i^2-2cdotfrac{sum^2}{m}+frac{sum^2}{m}
ight)\
=&m left(sumlimits_{i=1}^mv_i^2-frac{sum^2}{m}
ight)\
=&m sumlimits_{i=1}^mv_i^2-sum^2\
end{split}
]
很明显 (-sum^2) 是定值,所以要求 (left(m^2 imes E ight)_{min}),应该先求 (left(sumlimits_{i=1}^mv_i^2 ight)_{min})。
考虑到 (n) 很迷你,可以 ( exttt{dp})。
(F_{t,i}) 表示前 (t) 段包含 (a_1sim a_i) 的 (left(sumlimits_{h=1}^tv_h^2 ight)_{min})。
假设 (v_t=sum_{h=j+1}^ia_h)。
可以有递推式:
[F_{t,i}=min{F_{t-1,j}+v_t^2}=min{F_{t-1,j}+left(sum_{h=j+1}^ia_h
ight)^2}(jle i)
]
如果 (s_i=sumlimits_{h=1}^ia_h),用 (f) 表示 (F_t),用 (g) 表示 (F_{t-1}),那么上式变为:
[f_i=min{g_j+(s_i-s_j)^2}(jle i)
]
考虑 (j=k) 比 (j=t) 更优的情况:
[egin{split}
g_k+(s_i-s_k)^2<& g_t+(s_i-s_t)^2\
g_k+s_i^2-2s_is_k+s_k^2<& g_t+s_i^2-2s_is_t+s_t^2\
g_k-2s_is_k+s_k^2<& g_t-2s_is_t+s_t^2\
(g_k+s_k^2)-(g_t+s_t^2)<& 2s_is_k-2s_is_t\
frac{(g_k+s_k^2)-(g_t+s_t^2)}{s_k-s_t}<& 2s_i
end{split}
]
然后老套路,把 ((g_j,g_j+s_j^2)) 当做点,单调队列维护一个下凸壳,实现 ( exttt{dp})。
re int l,r; re vector<int> q(n+7);
for(re int i=1;i<=n;i++) dp[1][i]=p2(sm[i]);
for(re int t=2;t<=m;t++){
f=dp[t&1],g=dp[(t&1)^1],l=1,r=0,q[++r]=0; //奇淫技巧①:用指针 f,g 来定位滚动数组
for(re int i=1;i<=n;i++){
while(l<r&&slope(q[l],q[l+1])<=2.0*sm[i]) l++;
//奇淫技巧②:取min维护下凸壳,用<=,取max维护上凸壳,用>=
f[i]=g[q[l]]+p2(sm[i]-sm[q[l]]);
while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) r--; //奇淫技巧③:递推前后两句话用不同比较符号
q[++r]=i;
}
}
最后的答案就是 (mcdot f_n-sum^2)。
时间复杂度 (Theta(mn)),空间复杂度 (Theta(n))。
Code
#include <bits/stdc++.h>
using namespace std;
//Start
#define re register
#define il inline
#define mk make_pair
#define pb push_back
#define db double
#define lng long long
#define fi first
#define se second
const int inf=0x3f3f3f3f;
const lng INF=0x3f3f3f3f3f3f3f3f;
//Data
const int N=3000; /* dp[i][j]:i-th day ***/
int n,m,dp[2][N+7],*f,*g; /** j-th section **/
vector<int> a,sm; /*** val min sum()^2 */
//DP
template<typename T>il T p2(re T x){return x*x;}
il db slope(re int x,re int y){ //斜率函数用double
return 1.0*((g[x]+p2(sm[x]))-(g[y]+p2(sm[y])))/(sm[x]-sm[y]);
}
il int DP(){
re int l,r; re vector<int> q(n+7);
for(re int i=1;i<=n;i++) dp[1][i]=p2(sm[i]);
for(re int t=2;t<=m;t++){
f=dp[t&1],g=dp[(t&1)^1],l=1,r=0,q[++r]=0;
for(re int i=1;i<=n;i++){
while(l<r&&slope(q[l],q[l+1])<=2.0*sm[i]) l++;
f[i]=g[q[l]]+p2(sm[i]-sm[q[l]]);
while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) r--;
q[++r]=i;
}
}
return f[n];
}
//Main
int main(){
scanf("%d%d",&n,&m),a=sm=vector<int>(n+7);
for(re int i=1;i<=n;i++) scanf("%d",&a[i]),sm[i]=sm[i-1]+a[i];
printf("%d
",m*DP()-p2(sm[n])); //别忘了求最终答案啊
return 0;
}
祝大家学习愉快!