[NOIP2021] 方差
前言
\(\texttt{p}\color{red}{\texttt{igstd}}\) 场切的题,我到现在才来补。
导致我爆炸的题。
大意
给你一个非降的序列 \(a\),每次可以将 \(a_i\) 变成 \(a_{i-1}+a_{i+1}-a_i\),求若干次操作之后方差最小是多少,输出最小值乘 \(n^2\)。
Sol
首先是大家都知道的,这一次操作相当于交换差分数组中的两个元素。说明我们可以对差分数组重排,虽然但是我并不知道排成什么样子是最小的。
\(\texttt{p}\color{red}{\texttt{igstd}}\) 告诉我们,要手模样例。然后你发现样例是一个长度小于等于 \(3\) 的勾八。然后你写了个暴力跑了个 \(n=50\) 的手造高质量数据,发现在差分数组先变小后变大的时候是非常优秀的(也就是原数组在这种情况下非常均匀)!但是你发现你根本不会证这个东西,于是你考场上就 30pts 滚粗了。。。
题解区一大片的显然和容易证明搞得我很无所适从啊~有个大佬看起来是证明了这个东西。
我不会证,但是 OI 不就是只用不证的么(((。现在我们假设已经证明了这个结论,那么我们容易想到一个 \(2^n\) 的暴力做法,就是从小到大依次枚举方差,看是放在最低点的左边还是右边。然后这东西看着就很可以大炮。
令 \(dp[i][j]\) 表示考虑前 \(i\) 个方差后,当前原数列和为 \(j\) 的最小的原数列平方和(因为我们知道方差表达式为平方的平均数减去平均数的平方)。然后考虑大力转移。
如果当前这个放在最左边,那么最后的和会加上 \(d_i\times i\),平方和呢?考虑在前面放一个差分实际上是在整个数列上加上这个数,那就是考虑 \(a^2\to (a+d)^2=a^2+2ad+d^2\),所以答案应该加上和乘上这个差分的两倍再加上这个差分的平方。所以转移就是:
同样的,如果是放在后面可以轻易地进行转移,此时,上一个状态应该要减去 \(pre_i\),即 \(d_i\) 的前缀和。对答案的影响也就是多了一个平方,比上面那个要好推。
然后你考虑这么做的复杂度是多少,你发现是 \(O(n^2\times a)\) 的,会寄,然后你想了想,如果当前的方差是 \(0\),对大炮状态不会产生任何影响,所以不转移也罢。这就变成了 \(O(n\times a^2)\) 了,就过了。
空间好像需要滚一下。
Code
// Problem: P7962 [NOIP2021] 方差
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7962
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// Time: 2022-04-14 22:04:18
#include<bits/stdc++.h>
#define int long long
#define inf (1<<30)
#define INF (1ll<<60)
#define pb emplace_back
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define rep(i,j,k) for(int i=(j);i<=(k);i++)
#define per(i,j,k) for(int i=(j);i>=(k);i--)
#define pt(a) cerr<<#a<<'='<<a<<' '
#define pts(a) cerr<<#a<<'='<<a<<'\n'
using namespace std;
const int MAXN=1e4+10;
int dp[2][MAXN*600],a[MAXN],d[MAXN],pre[MAXN];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int n,sum=0;cin>>n;
rep(i,1,n) cin>>a[i];
rep(i,1,n-1) d[i]=a[i+1]-a[i];
sort(d+1,d+n);
rep(i,1,n-1) sum+=d[i]*i,pre[i]=pre[i-1]+d[i];
int cnt=0;
while(!d[cnt+1]) cnt++;
rep(j,1,sum) dp[cnt&1][j]=INF;
rep(i,cnt+1,n-1){
rep(j,0,sum){
dp[i&1][j]=INF;
if(j-d[i]*i>=0)
dp[i&1][j]=min(dp[i&1][j],dp[(i-1)&1][j-d[i]*i]+(j-d[i]*i)*d[i]*2+i*d[i]*d[i]);
if(j-pre[i]>=0)
dp[i&1][j]=min(dp[i&1][j],dp[(i-1)&1][j-pre[i]]+pre[i]*pre[i]);
// if(dp[i&1][j]<INF) pt(i),pt(j),pts(dp[i&1][j]);
}
}
int ans=INF;
rep(j,0,sum)
if(dp[(n-1)&1][j]<INF)
ans=min(ans,n*dp[(n-1)&1][j]-j*j);
cout<<ans<<'\n';
return 0;
}