[NOIP2021] 方差 题解

[NOIP2021] 方差 题解


知识点

差分,最小值背包 DP,模拟退火

分析

首先,定义 \(\{ a_i \}\) 的差分数组 \(\{ d_i \}\),其中 \(d_i = a_{i+1} - a_i,i\in [1,n)\)

那么 \(a_i \gets a_{i-1} + a_{i+1} - a_i\) 的操作等价于交换 \(d_{i-1},d_i\)

要让方差最小,那么我们直接按照它的应用可以反向推导:方差越小,数列起伏越小。那么只要让数列起伏较小即可。

但是这样直接构造似乎不太行,毕竟那样算是贪心,这里不能用。

考虑 \(n^2 D\) 的式子:\(n\sum a_i^2-(\sum a_i)\)

那么我们就直接上最小值背包 DP,将 \(d_i\) 从小到大排序,从中间开始放,决策就考虑放在中间这块前面还是后面。

\(f_{i,j}\) 表示放到第 \(i\) 个差分值且 \(\sum a_i = j\) 时,\(\sum a_i^2\) 的最小值是多少。

转移:加入 \(d_i\)

  1. 放在前面:那么每个值都会加 \(d_i\)

    \[f_{j+d_i \times i} \gets f_{j} + 2 j d_i + i d_i^2 \\ \]

  2. 放在后面:加入一个值 \(\sum_{k=1}^i d_k\),那么我们处理出前缀和数组 \(pre_k = \sum_{k=1}^i d_k\)

    \[f_{j+pre_i} \gets f_{j} + pre_i^2 \]

最后可以很容易算出答案:

\[\min {(n f_j - j^2)} \\ \]

还有一点:现在的时间复杂度是 \(O(n^2 a_{\max})\) 的,这样不能过掉全部点,但是我们发现那些点的 \(n\) 虽然大,但是 \(a_{\max}\) 小,说明差分数组中有很多 \(0\),而转移时遇到 \(0\) 是没用的,我们可以遇到时直接跳过,这样时间复杂度就优化到了 \(O(n a_{\max} \min{(n,a_{\max})})\)

代码

时间复杂度:\(O(n a_{\max} \min{(n,a_{\max})})\),空间复杂度:\(O(n a_{\max})\)

#define Plus_Cat "variance"
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define ll long long
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define FOR(i,a,b) for(int i(a);i<=(int)(b);++i)
#define DOR(i,a,b) for(int i(a);i>=(int)(b);--i)
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define EDGE(g,i,x,y) for(int i(g.h[x]),y(g[i].v);~i;y=g[i=g[i].nxt].v)
using namespace std;
constexpr int N(1e4+10),S(5e5+10);
int n,lim;
int a[N],d[N],pre[N];
ll ans(LINF);
ll f[S];
int main() {
#ifdef Plus_Cat
	freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
	scanf("%d",&n);
	FOR(i,1,n)scanf("%d",&a[i]);
	FOR(i,1,n-1)d[i]=a[i+1]-a[i];
	sort(d+1,d+n),RCL(f,0x3f,f,1),f[0]=0;
	FOR(i,1,n-1)pre[i]=pre[i-1]+d[i];
	FOR(i,1,n-1)if(d[i])DOR(j,lim,0)if(f[j]<LINF) {
			tomin(f[j+i*d[i]],f[j]+(ll)2*d[i]*j+(ll)d[i]*d[i]*i);
			tomin(f[j+pre[i]],f[j]+(ll)pre[i]*pre[i]);
			tomax(lim,j+i*d[i],j+pre[i]),f[j]=LINF;
		}
	FOR(i,0,lim)if(f[i]<LINF)tomin(ans,(ll)n*f[i]-(ll)i*i);
	printf("%lld\n",ans);
	return 0;
}

posted @ 2024-11-13 13:54  plus_cat  阅读(2)  评论(0编辑  收藏  举报