P7962 [NOIP2021] 方差
[NOIP2021] 方差
时隔一年。我又回来做这个题了。。。
我们通过观察是可以发现这里的操作实际上就是交换相邻差分,但是差分 \(c_1\) 不可被交换。
然后如果要求方差最小的话我们可以想到这个差分应该是单谷的。当然,实际上这就是一个直觉的结论。
想要证明的话可以从调整法入手,我们交换这个单谷的两个差分使其变成非单谷的状态,会发现方差一定会更大。
所以说我们把第一个差分数组固定后,剩下的差分数组从小到大排列,要么放在现有数列的最前面,要么放在最后面。
这个单谷的形态不可考察,我们有必要用 DP 来寻找最佳。
定义 \(f(i,s)\) 表示考虑了前 \(i\) 个差分数组(排序后),此时序列的 \(\sum a_i = s\) 时的最小的 \(\sum a _ i ^ 2\)。
考虑转移:
\[f (i-1,s) + \left(\sum_ {j=1} ^ {i} c_j \right) ^ 2\rightarrow f\left (i,s+ \left(\sum_ {j=1} ^ {i} c_j \right) \right )
\]
\[f(i-1,s) + (i-1)c _ i ^ 2 +2 c_i s + c _ 1 ^ 2 \rightarrow f( i , s + (i-1)c_i + c_1)
\]
我们知道每次转移是 \(O(na)\) 的,那么直接从头至尾做就是 \(O( n ^ 2 a)\) 的。
考虑到 \(a\) 的范围不大,实际上 $c_i = 0 $ 的情况很多,而 \(c_i \ne 0\) 的情况至多只有 \(O(a)\) 的数量级。
而 \(c_i=0\) 都排在前面,我们提前铺开处理掉就可以了。
这样复杂度就是 \(O(na^2)\) 的,数据范围大概印证了这个方法就是相当的正解。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#define ll long long
using namespace std;
namespace Ehnaev{
inline ll read() {
ll ret=0,f=1;char ch=getchar();
while(ch<48||ch>57) {if(ch==45) f=-f;ch=getchar();}
while(ch>=48&&ch<=57) {ret=(ret<<3)+(ret<<1)+ch-48;ch=getchar();}
return ret*f;
}
inline void write(ll x) {
static char buf[22];static ll len=-1;
if(x>=0) {do{buf[++len]=x%10+48;x/=10;}while(x);}
else {putchar(45);do{buf[++len]=-(x%10)+48;x/=10;}while(x);}
while(len>=0) putchar(buf[len--]);
}
}using Ehnaev::read;using Ehnaev::write;
const ll N=1e6,M=1e6,inf=1e16;
ll n,m,sum,now,ans;
ll a[N+5],c[N+5],g[2][M+5];
int main() {
n=read();
for(ll i=1;i<=n;i++) {
a[i]=read();c[i]=a[i]-a[i-1];m=max(m,a[i]);
}
sort(c+2,c+n+1);
m=m*n;sum=c[1];
for(ll i=1;i<=m;i++) g[now][i]=inf;
g[now][sum]=sum*sum;
for(ll i=2;i<=n;i++) {
sum+=c[i];
if(c[i]!=0) {
for(ll j=1;j<=m;j++) {g[1-now][j]=inf;}
for(ll j=1;j<=m;j++) {
g[1-now][j+sum]=min(g[1-now][j+sum],g[now][j]+sum*sum);
g[1-now][j+c[i]*(i-1)+c[1]]=min(g[1-now][j+c[i]*(i-1)+c[1]]
,g[now][j]+(i-1)*c[i]*c[i]+2*c[i]*j+c[1]*c[1]);
}
now=1-now;
}
else {
g[now][sum*(i-1)]=inf;g[now][sum*i]=sum*sum*i;
continue;
}
// for(ll j=1;j<=m;j++) {
// printf("Test: %lld %lld\n",j,g[now][j]);
// }
}
ans=inf;
for(ll i=1;i<=m;i++) {
// printf("Test: %lld %lld\n",i,g[now][i]);
ans=min(ans,n*g[now][i]-i*i);
}
write(ans);
return 0;
}