[NOIP2021] 方差
前置知识:CF1110E。
可以看看写的 \(\textbf{题解}\)。
题目大意
给定长度为 \(n\) 的非严格递增正整数数列 \(1 \le a_1 \le a_2 \le \cdots \le a_n\)。每次可以进行的操作是:任意选择一个正整数 \(1 \lt i \lt n\),将 \(a_i\) 变为 \(a_{i - 1} + a_{i + 1} - a_i\)。求在若干次操作之后,该数列的方差最小值是多少。请输出最小值乘以 \(n^2\) 的结果。
其中方差的定义为:数列中每个数与平均值的差的平方的平均值。更形式化地说,方差的定义为 \(D = \frac{1}{n} \sum_{i = 1}^{n} {(a_i - \bar a)}^2\),其中 \(\bar a = \frac{1}{n} \sum_{i = 1}^{n} a_i\)。
题目分析
前置知识中提到:
令 \(c\) 为差分数组,且 \(c_i=a_{i+1}-a_i(1\le i\lt n)\)。
则一次操作 \(a_i=a_{i-1}+a_{i+1}-a_i\) 等价于交换 \(c_i\) 和 \(c_{i+1}\),那么问题转换为:
给出长度为 \(n\) 的序列,重组原序列的差分数组,将得到的差分数组再求出 \(a\) 数组。求 \(n^2\times\min\{\dfrac{1}{n}\sum\limits_{i=1}^{n}(a_i-\bar a)^2\}\)。
显然可以 \(\operatorname{O}(N!)\) 枚举。
有一个重要的性质:答案差分数组一定先递减再递增。
于是可以随机化乱搞,\(84\) 分到手。
理一理式子:
发现 \((a_1+a_2+\cdots+a_n)\) 就是 \(n\times\bar a\),于是:
由于 \(n^2\times \bar a^2\) 等于 \((n\times \bar a)^2\),即 \((\sum\limits_{i=1}^n a_i)^2\),故有:
令 \(m=n-1\),\(dp[i][j]\) 表示当前考虑了差分数组的前 \(i\) 个时的 \(\sum\limits_{i=1}^n a_i\)。
这样转移会很方便,因为我们要想让 \(a\) 数组满足先减再增的性质,故只需要从小到大排序,然后依次考虑放左边还是放右边就好了。
\(c_i\) 放在右边时:
令 \(now=\sum\limits_{j=1}^i c_j\),则 \(a_i=now\)(差分的性质)。
方程为 \(dp[i][j]\gets dp[i-1][j-now]+now^2\)。
\(c_i\) 放在左边时:
\(a\) 数组中这 \(i\) 个数都会加上 \(c_i\),总共加了 \(i\times c_i\)。此时平方和的变化量为
设此时(转移之前)\(dp\) 数组的第二个状态为 \(p\)。
方程为 \(dp[i][j]\gets dp[i-1][j-i\times c_i]+2\times c_i\times (j-i\times c_i)+i\times c_i^2\)。
代码
//2021/12/4
//2021/12/6
//2021/12/7
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
#include <climits>//need "INT_MAX","INT_MIN"
#include <algorithm>
#include <cstring>
#define int long long
#define enter() putchar(10)
#define debug(c,que) cerr<<#c<<" = "<<c<<que
#define cek(c) puts(c)
#define blow(arr,st,ed,w) for(register int i=(st);i<=(ed);i++)cout<<arr[i]<<w;
#define speed_up() cin.tie(0),cout.tie(0)
#define endl "\n"
#define Input_Int(n,a) for(register int i=1;i<=n;i++)scanf("%d",a+i);
#define Input_Long(n,a) for(register long long i=1;i<=n;i++)scanf("%lld",a+i);
#define mst(a,k) memset(a,k,sizeof(a))
namespace Newstd
{
inline int read()
{
int x=0,k=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-')
{
k=-1;
}
ch=getchar();
}
while(ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*k;
}
inline void write(int x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9)
{
write(x/10);
}
putchar(x%10+'0');
}
}
using namespace Newstd;
using namespace std;
const int INF=0x3f3f3f3f3f3f3f3f;
const int ma=500005;
int a[ma],b[ma];
int dp[2][ma];
int n,sum;
#undef int
int main(void)
{
#define int long long
n=read();
for(register int i=1;i<=n;i++)
{
a[i]=read();
}
for(register int i=1;i<n;i++)
{
b[i]=a[i+1]-a[i];
}
sort(b+1,b+n);
for(register int i=1;i<n;i++)
{
sum+=b[i]*i;
}
mst(dp[0],0x3f);
dp[0][0]=0;
int now(0),idx(0);
for(register int i=1;i<n;i++)
{
if(b[i]==0)
{
continue;
}
idx^=1ll;
mst(dp[idx],0x3f);
now+=b[i];
for(register int j=0;j<=sum;j++)
{
if(j-i*b[i]>=0)
{
dp[idx][j]=min(dp[idx][j],dp[idx^1ll][j-i*b[i]]+i*b[i]*b[i]+2*b[i]*(j-i*b[i]));
}
if(j-now>=0)
{
dp[idx][j]=min(dp[idx][j],dp[idx^1ll][j-now]+now*now);
}
}
}
int ans=INF;
for(register int i=0;i<=sum;i++)
{
if(dp[idx][i]<INF)
{
ans=min(ans,n*dp[idx][i]-i*i);
}
}
printf("%lld\n",ans);
return 0;
}

浙公网安备 33010602011771号