[NOIP2021] 方差题解
brokenblade · · 题解
要满足改变后的结果减小,必须保证
把
考虑交换两个相邻数组的差分值,即
考虑当
这个时候因为
考虑当
这个时候因为
注意这个时候的
最优解的差分数组必定是成单谷状的。
做法
证明了这样的一个优秀的性质之后,一切都变得好办了。
然后考虑维护单谷,先对差分数组进行排序,也即是每一个
然后发现这玩意儿可以直接
后插柿子:
前插柿子:
这两个柿子还是比较好理解的。
同时我们发现下一个
但是这样的时间复杂度不就是
但仔细想想,我们可以对前面多个
于是就写出了代码,巨好写:
#include<bits/stdc++.h>
#define int long long
#define Debug if(false)
#define rnt register int
using namespace std;
const int maxn=10100,inf=2e9;
inline int read()
{
int x=0,f=1;char c;
for(;!isdigit(c);c=getchar()) if(c=='-') f=-f;
for(;isdigit(c);c=getchar()) x=(x<<1)+(x<<3)+(c^48);
return x*f;
}
int a[maxn],dif[maxn],sum[maxn],n=read();
int dp[2][500010],maxa;
signed main()
{
for(rnt i=1;i<=n;i++) a[i]=read();
for(rnt i=1;i<n;i++) dif[i]=a[i+1]-a[i];
sort(dif+1,dif+n),maxa=a[n];int cnt0=1;
for(rnt i=1;i<n;i++) if(!dif[i]) cnt0++;
for(rnt i=1;i<n;i++) sum[i]=sum[i-1]+dif[i];
int now=0,las=1,lim=maxa*n;
for(rnt i=0;i<=lim;i++) dp[now][i]=inf; //初始全都设为inf
dp[now][dif[cnt0]]=dif[cnt0]*dif[cnt0]; //首值放尾
dp[now][dif[cnt0]*cnt0]=dif[cnt0]*dif[cnt0]*cnt0; //首值放头
for(rnt i=cnt0+1;i<n;i++)
{
now^=1,las^=1;
for(rnt s=0;s<=lim;s++) dp[now][s]=inf;
for(rnt s=0;s<=lim;s++)
{
if(dp[las][s]==inf) continue;
dp[now][s+dif[i]*i]=min(dp[now][s+dif[i]*i],dp[las][s]+2*s*dif[i]+dif[i]*dif[i]*i); //放头
dp[now][s+sum[i]]=min(dp[now][s+sum[i]],dp[las][s]+sum[i]*sum[i]); //放尾
}
}
int ans=inf;
for(rnt i=0;i<=maxa*n;i++)
if(dp[now][i]!=inf) ans=min(ans,n*dp[now][i]-i*i);
cout << ans << endl;
return 0;
}
总结
首先想到差分必须有一定的手玩技巧。其次就是重要的性质单谷。
这道题想到单谷可能很简单,但是在证明的时候总是差那么一点。我认为此题的思维难度还是比较高的,至少对于我这种菜鸡来说。