Problem:
题目描述
给定长度为 \(n\) 的非严格递增正整数数列 \(1 \le a_1 \le a_2 \le \cdots \le a_n\)。每次可以进行的操作是:任意选择一个正整数 \(1 < i < 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\)。
输入格式
输入的第一行包含一个正整数 \(n\),保证 \(n \le {10}^4\)。
输入的第二行有 \(n\) 个正整数,其中第 \(i\) 个数字表示 \(a_i\) 的值。数据保证 \(1 \le a_1 \le a_2 \le \cdots \le a_n\)。
输出格式
输出仅一行,包含一个非负整数,表示你所求的方差的最小值的 \(n^2\)
样例 #1
样例输入 #1
4
1 2 4 6
样例输出 #1
52
样例 #2
样例输入 #2
见附件中的 variance/variance2.in
样例输出 #2
见附件中的 variance/variance2.ans
样例 #3
样例输入 #3
见附件中的 variance/variance3.in
样例输出 #3
见附件中的 variance/variance3.ans
样例 #4
样例输入 #4
见附件中的 variance/variance4.in
样例输出 #4
见附件中的 variance/variance4.ans
提示
【样例解释 #1】
对于 \((a_1, a_2, a_3, a_4) = (1, 2, 4, 6)\),第一次操作得到的数列有 \((1, 3, 4, 6)\),第二次操作得到的新的数列有 \((1, 3, 5, 6)\)。之后无法得到新的数列。
对于 \((a_1, a_2, a_3, a_4) = (1, 2, 4, 6)\),平均值为 \(\frac{13}{4}\),方差为 \(\frac{1}{4}({(1 - \frac{13}{4})}^2 + {(2 - \frac{13}{4})}^2 + {(4 - \frac{13}{4})}^2 + {(6 - \frac{13}{4})}^2) = \frac{59}{16}\)。
对于 \((a_1, a_2, a_3, a_4) = (1, 3, 4, 6)\),平均值为 \(\frac{7}{2}\),方差为 \(\frac{1}{4} ({(1 - \frac{7}{2})}^2 + {(3 - \frac{7}{2})}^2 + {(4 - \frac{7}{2})}^2 + {(6 - \frac{7}{2})}^2) = \frac{13}{4}\)。
对于 \((a_1, a_2, a_3, a_4) = (1, 3, 5, 6)\),平均值为 \(\frac{15}{4}\),方差为 \(\frac{1}{4} ({(1 - \frac{15}{4})}^2 + {(3 - \frac{15}{4})}^2 + {(5 - \frac{15}{4})}^2 + {(6 - \frac{15}{4})}^2) = \frac{59}{16}\)。
【数据范围】
测试点编号 | \(n \le\) | \(a_i \le\) |
\(1 \sim 3\) | \(4\) | \(10\) |
\(4 \sim 5\) | \(10\) | \(40\) |
\(6 \sim 8\) | \(15\) | \(20\) |
\(9 \sim 12\) | \(20\) | \(300\) |
\(13 \sim 15\) | \(50\) | \(70\) |
\(16 \sim 18\) | \(100\) | \(40\) |
\(19 \sim 22\) | \(400\) | \(600\) |
\(23 \sim 25\) | \({10}^4\) | \(50\) |
对于所有的数据,保证 \(1 \le n \le {10}^4\),\(1 \le a_i \le 600\)。
Solution:
好题,考场上蒙了,冷静分析一下其实并不是很难?
首先这个操作将 \(a_i\) 变为 \(a_{i-1}+a_{i+1}-a_i\)
考虑 \(a_i\) 的差分数组 \(b_i=a_i-a_{i-1}\),那么将 \(a_i\) 变为 \(a_{i-1}+a_{i+1}-a_i\) 后,\(b'_i=(a_{i-1}+a_{i+1}-a_i)-a_{i-1}=a_{i+1}-a_i,b'_{i+1}=a_{i+1}-(a_{i-1}+a_{i+1}-a_i)=a_i-a_{i-1}\),我们惊奇地发现了一件事情,即 \(b'_i=b_{i+1},b'_{i+1}=b_i\)!
所以说这个操作本质上就相当于交换相邻的差分数组,由于操作次数任意,所以我们就相当于我们可以将差分数组任意重排!
能任意重排之后我们又陷入了瓶颈之中,我们似乎只能想到 \(O(nn!)\)
如何优化?
这需要我们不断地探寻性质,我们仔细观察样例,发现样例最优答案对应的差分数组是 \(2\,\,1\,\,2\)
但是大胆猜想,小心求证,如果你的数学能力不足以验证猜想,那就使用 OI 的方式来基本验证。
我们发现样例 2 的 \(n=10\),我们可以用我们先前所述的 \(O(nn!)\)
我们发现仍然呈单谷状,再辅以感性理解(方差最小要求答案尽量平均),我们基本可以断定。
那么接下来如何利用单谷的性质。
已经在谷底了,怎么走,都是向上,所以......
我们直接看做单谷是从谷向两边不断扩展,即每次一个新的差分添加在目前构成的差分序列的左/右侧。
然后相比之前没找到这条性质不同的点在于,我们知道了差分数组的 添加顺序,这对于我们来说非常重要。
那就开始设计 dp 状态吧,我们需要明确我们需要记录哪些量,我们知道对于一个数列其方差的 \(n^2\) 倍 \(n^2D=n(\sum a_i^2)+(\sum a_i)^2\)。
所以说我们需要同时维护一个序列的和和平方和。
直接设 \(dp_i\) 表示差分数组中已经加了前 \(i\) 小的差分所得到的最小 \(n^2 D\)?
状态设置得很好,就是没有办法转移。
怎么办呢,我们考虑将 \(\sum a_i\)
设 \(dp_{i,j}\) 表示差分数组中已经加了前 \(i\) 小的差分所得到的的最小 \(\sum{a_i^2}\) (因为 \(n^2D=n(\sum a_i^2)+(\sum a_i)^2\) 中的 \(n\) 是定值,\(\sum a_i\) 被我们写进状态所以 \((\sum a_i)^2\) 也变为了定值,故最小化 \(n^2D\) 即最小化 \(\sum a_i^2\))。
转移比较简单,分新的差分加在目前形成的差分序列的前后两类分类讨论,读者可自行推导,并非这题的精髓所在,故略过。
这样之后总状态数是 \(O(n^2\operatorname{maxa})\)
如何优化呢,第二维的 \(\sum a_i\) 肯定是动不了了,考虑第一维,我们发现 \(n\) 大 \(\operatorname{maxa}\) 小,而非零的差分数组不是 \(O(n)\) 级别的,而是 \(O(\operatorname{maxa})\)
第一维我们只需要将第 \(i\) 个差分数组非零的 \(i\) 纳入状态即可(为什么呢,因为前面所有 \(0\) 都只能构成 \(\sum a_i=\sum a_i^2=0\)
所以总状态数变为了 \(O(\operatorname{maxa}^2n)\)
空间的话滚动数组滚一下即可。
#include<bits/stdc++.h>
using namespace std;
#define inf 0x7fffffff
#define timeused() (double)clock()/CLOCKS_PER_SEC
#define rep(i,a,b) for(register int i=a,i##end=b;i<=i##end;++i)
#define repp(i,a,b) for(register int i=a,i##end=b;i>=i##end;--i)
#define mp make_pair
#define pb push_back
typedef long long ll;
typedef unsigned long long ull;
template<typename T> inline T rd(T& x){
T f=1;x=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(T)(c-'0');
x*=f;
return x;
}
ll n,a[10005],cf[10005],dp[500005],nw[500005],cnt,qzh[10005];
int main(){
rd(n);
rep(i,1,n) rd(a[i]);
rep(i,1,n) cf[i]=a[i]-a[i-1];
cf[1]=0;
sort(cf+2,cf+n+1);
if(!cf[n]){
putchar('0');
return 0;
}
rep(i,1,n) qzh[i]=qzh[i-1]+cf[i];
rep(i,2,n){
if(!cf[i]) continue;
rep(j,0,n*a[n]){
if(j&&!cf[i-1]){
nw[j]=dp[j]=1ll*inf*inf;
continue;
}
dp[j]=nw[j];
nw[j]=1ll*inf*inf;
}
rep(j,0,n*a[n]){
if(dp[j]==1ll*inf*inf) continue;
nw[j+qzh[i]]=min(nw[j+qzh[i]],dp[j]+qzh[i]*qzh[i]);
nw[j+cf[i]*(i-1)]=min(nw[j+cf[i]*(i-1)],dp[j]+cf[i]*cf[i]*(i-1)+2*cf[i]*j);
}
}
ll minn=1ll*inf*inf;
rep(i,0,n*a[n]){
if(nw[i]==1ll*inf*inf) continue;
minn=min(minn,n*nw[i]-1ll*i*i);
}
printf("%lld\n",minn);
}