正题
题目链接:https://www.luogu.com.cn/problem/P7962
题目大意
给出一个长度为nnn的序列aaa,你每次可以让一个ai(1<i<n)=ai−1+ai+1−aia_i(1<i<n)=a_{i-1}+a_{i+1}-a_iai(1<i<n)=ai−1+ai+1−ai,求能变出的最小方差。
1≤n≤400,1≤ai≤6001\leq n\leq 400,1\leq a_i\leq 6001≤n≤400,1≤ai≤600或1≤n≤104,1≤ai≤501\leq n\leq 10^4,1\leq a_i\leq 501≤n≤104,1≤ai≤50
解题思路
民间数据过了就当过了吧
这个式子显然我们可以差分之后变成交换相邻的数,让所有的数都减去a1a_1a1这样方差不变并且第一个保证为000,然后我们记差分数组bi=ai+1−aib_i=a_{i+1}-a_ibi=ai+1−ai,那么化一下答案的式子就是
n∑i=1nai2−(∑i=1nai)2⇒n∑i=1n−1(∑j=1ibj)2−(∑i=1n−1bi(n−i))2n\sum_{i=1}^na_i^2-\left(\sum_{i=1}^na_i\right)^2\Rightarrow n\sum_{i=1}^{n-1}\left(\sum_{j=1}^{i}b_j\right)^2-\left(\sum_{i=1}^{n-1}b_i(n-i)\right)^2ni=1∑nai2−(i=1∑nai)2⇒ni=1∑n−1(j=1∑ibj)2−(i=1∑n−1bi(n−i))2
这个式子乍一眼我们看不出什么,但是我们可以得到每个bib_ibi对之间乘积的权重记为fi,jf_{i,j}fi,j,会发现fi,jf_{i,j}fi,j是按照i/ji/ji/j相互之间越接近/越接近中间而递增的,但是依然会出现一些边边的靠近数对的情况比中间的不那么靠近数对的权重要高。
一个直观的想法是类似于一个山谷之类的填法,打几个表之后不难发现确实是从一个点往两边递增的规律。
考虑在此基础上进行dpdpdp,考虑在一个填好的序列的前面/后面加上一个数字会产生的变化,记ansansans为原来的答案,记LLL为题目中给出的nnn(因为目前我们还没有放完nnn个数字,所以此时的nnn不一样),xxx为插入的数组。
- 插在前面:
L∑i=1n−1(∑j=1ibj+x)2−(nx+∑i=1n−1bi(n−i))2L\sum_{i=1}^{n-1}\left(\sum_{j=1}^{i}b_j+x\right)^2-\left(nx+\sum_{i=1}^{n-1}b_i(n-i)\right)^2Li=1∑n−1(j=1∑ibj+x)2−(nx+i=1∑n−1bi(n−i))2
⇒ans+Lnx2+2Lx∑i=1n−1bj−2nx∑i=1n−1bi(n−i)−n2x2\Rightarrow ans+Lnx^2+2Lx\sum_{i=1}^{n-1}b_j-2nx\sum_{i=1}^{n-1}b_i(n-i)-n^2x^2⇒ans+Lnx2+2Lxi=1∑n−1bj−2nxi=1∑n−1bi(n−i)−n2x2 - 插在后面:
L(∑i=1n−1(∑j=1ibj)2+(x+∑i=1n−1bi)2)−(x+∑i=1n−1bi+∑i=1n−1bi(n−i))2L\left(\sum_{i=1}^{n-1}\left(\sum_{j=1}^{i}b_j\right)^2+\left(x+\sum_{i=1}^{n-1}b_i\right)^2\right)-\left(x+\sum_{i=1}^{n-1}b_i+\sum_{i=1}^{n-1}b_i(n-i)\right)^2L⎝⎛i=1∑n−1(j=1∑ibj)2+(x+i=1∑n−1bi)2⎠⎞−(x+i=1∑n−1bi+i=1∑n−1bi(n−i))2
⇒ans+L(x+∑i=1n−1bi)2−(x+∑i=1n−1bi)2−2(x+∑i=1n−1bi)(∑i=1n−1bi(n−i))\Rightarrow ans+L\left(x+\sum_{i=1}^{n-1}b_i\right)^2-\left(x+\sum_{i=1}^{n-1}b_i\right)^2-2\left(x+\sum_{i=1}^{n-1}b_i\right)\left(\sum_{i=1}^{n-1}b_i(n-i)\right)⇒ans+L(x+i=1∑n−1bi)2−(x+i=1∑n−1bi)2−2(x+i=1∑n−1bi)(i=1∑n−1bi(n−i))
然后会发现我们很难知道∑i=1n−1bi(n−i)\sum_{i=1}^{n-1}b_i(n-i)∑i=1n−1bi(n−i)这个东西,所以考虑放进dpdpdp数组里面维护,但是如果丢进去维护了后面那个东西就完全没有必要了,所以可以删掉很多复杂的部分。
那么设fi,jf_{i,j}fi,j表示目前填了iii个∑i=1n−1bi(n−i)=j\sum_{i=1}^{n-1}b_i(n-i)=j∑i=1n−1bi(n−i)=j时的最小方差,然后就可以O(1)O(1)O(1)转移了。
这样的时间复杂度是O(n2ai)O(n^2a_i)O(n2ai)的,可以拿到888888分,我在考场上就止步于此了。
现在来分析一下最后一个点ai≤50a_i\leq 50ai≤50的性质,也就是说差分数组里面最多只有505050个数是有值的,所以有一堆000,直接动态更新dpdpdp枚举的上界就过了。
时间复杂度:O(naimin{ai,n})O(na_i\min\{a_i,n\})O(naimin{ai,n})
好不容易那么接近一次正解,你却让我输的那么彻底,焯!\color{white}好不容易那么接近一次正解,你却让我输的那么彻底,焯!好不容易那么接近一次正解,你却让我输的那么彻底,焯!
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=5e5+10,inf=1e18;
ll n,L,a[N],s[N],f[2][N],ans;
signed main()
{scanf("%lld",&n);if(n==1)return puts("0");for(ll i=1;i<=n;i++)scanf("%lld",&a[i]);L=n*a[n];ans=inf;for(ll i=1;i<n;i++)a[i]=a[i+1]-a[i];n--;sort(a+1,a+1+n);L=a[1];for(ll i=1;i<=n;i++)s[i]=s[i-1]+a[i];for(ll i=0;i<=L;i++)f[1][i]=inf;f[1][a[1]]=a[1]*a[1];for(ll k=2;k<=n;k++){int R=s[k]*k;for(ll i=0;i<=R;i++)f[k&1][i]=inf;for(ll i=0;i<=L;i++){if(f[~k&1][i]!=inf){f[k&1][i+a[k]*k]=min(f[k&1][i+a[k]*k],f[~k&1][i]+k*a[k]*a[k]+2ll*i*a[k]);f[k&1][i+s[k]]=min(f[k&1][i+s[k]],f[~k&1][i]+s[k]*s[k]);}}L=R;}for(ll i=0;i<=L;i++)if(f[n&1][i]!=inf)ans=min(ans,f[n&1][i]*(n+1)-i*i);printf("%lld\n",ans);return 0;
}
/*
10
6 19 34 35 56 63 82 82 83 99
*/