Loading

题解-[SDOI2016]征途

[SDOI2016]征途

[SDOI2016]征途

给定长度为 \(n\) 的序列 \(a\{n\}\),将其分为连续 \(m\) 段,和分别为 \(v\{m\}\)\(v\{m\}\) 的方差为 \(E\),求 \(\left(m^2\times E\right)_{\min}\)

\(1\le n\le 3000\)\(1\le \sum a_i\le 30000\)


复习斜率优化第一题,遇到好多麻烦,写一篇题解记录。


首先推式探索 \(m^2\times E\) 的本质:

\(sum=\sum a_i=\sum v_i\)

\[\begin{split} m^2\times E=&m^2\times \frac{\sum\limits_{i=1}^m\left(v_i-\frac{sum}{m}\right)^2}{m}\\ =&m \sum\limits_{i=1}^m\left(v_i-\frac{sum}{m}\right)^2\\ =&m \left(\sum\limits_{i=1}^mv_i^2-\sum\limits_{i=1}^m2v_i\cdot\frac{sum}{m}+\sum\limits_{i=1}^m\frac{sum^2}{m^2}\right)\\ =&m \left(\sum\limits_{i=1}^mv_i^2-2sum\cdot\frac{sum}{m}+m\cdot\frac{sum^2}{m^2}\right)\\ =&m \left(\sum\limits_{i=1}^mv_i^2-2\cdot\frac{sum^2}{m}+\frac{sum^2}{m}\right)\\ =&m \left(\sum\limits_{i=1}^mv_i^2-\frac{sum^2}{m}\right)\\ =&m \sum\limits_{i=1}^mv_i^2-sum^2\\ \end{split} \]

很明显 \(-sum^2\) 是定值,所以要求 \(\left(m^2\times E\right)_{\min}\),应该先求 \(\left(\sum\limits_{i=1}^mv_i^2\right)_{\min}\)


考虑到 \(n\) 很迷你,可以 \(\texttt{dp}\)

\(F_{t,i}\) 表示前 \(t\) 段包含 \(a_1\sim a_i\)\(\left(\sum\limits_{h=1}^tv_h^2\right)_{\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\right)^2\}(j\le i) \]

如果 \(s_i=\sum\limits_{h=1}^ia_h\),用 \(f\) 表示 \(F_t\),用 \(g\) 表示 \(F_{t-1}\),那么上式变为:

\[f_i=\min\{g_j+(s_i-s_j)^2\}(j\le i) \]


考虑 \(j=k\)\(j=t\) 更优的情况:

\[\begin{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)\) 当做点,单调队列维护一个下凸壳,实现 \(\texttt{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;
	}
}

最后的答案就是 \(m\cdot 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\n",m*DP()-p2(sm[n])); //别忘了求最终答案啊
	return 0;
}

祝大家学习愉快!

posted @ 2020-03-28 22:04  George1123  阅读(117)  评论(0编辑  收藏  举报