题面
具体题面就不拉了吧,这题应该挺有名的。
题解
首先有一个变换大家必须注意到,这个数组的差分数组可以任意重排。
然后,对它找找规律,不难发现,重排后的差分数组,大致是先递减后递增的,中间一堆 0,这是因为我们求的是最小方差,而方差是描述数据离散程度的量,
一定存在一个分界点,使得前面的差分值单减、后面的差分值单增。但是此时我仍然没有摆脱枚举 a ˉ \bar a aˉ 这个根深蒂固的想法……用 O ( n a ) \mathcal O(na) O(na) 枚举 a ˉ \bar a aˉ 之后,将差分值从大到小排序,从两边往中间填,状态为某一边的末尾 a a a 值;这样就是 O ( n 2 a 2 ) \mathcal O(n^2a^2) O(n2a2) 的了。
最小方差生成树中,边的权值之间非常不独立,让我们难以运用贪心求解最小生成树,所以我们才枚举 a ˉ \bar a aˉ。在这道题里,其实完全不需要枚举 a ˉ \bar a aˉ 了!化简一下 n 2 D n^2D n2D 的式子得到
n 2 D = n ∑ a i 2 − ( ∑ a i ) 2 n^2D=n\sum a_i^2-\left(\sum a_i\right)^2 n2D=n∑ai2−(∑ai)2
可以直接对它进行动态规划。从两边往中间放行不通了,考虑从中间往两边放。这相当于一个动态变化的过程,可以在前面插入差分值(将所有数增大这个差分值)或者在最后插入。奇特的是,将所有数增大 d d d 对 ∑ a i 2 \sum a_i^2 ∑ai2 的影响可以用 ∑ a i \sum a_i ∑ai 轻易求出!
于是状态中存储 ∑ a i \sum a_i ∑ai 即可。这样的复杂度是 O ( n 2 a ) \mathcal O(n^2a) O(n2a) 的,无法通过最后的测试点。
有哪里可以改进吗?剪枝呗!求出当前最大可行的 ∑ a i \sum a_i ∑ai 是多少,然后严格在范围内转移。这样就可以通过了,时间复杂度 O ( n a 2 ) \mathcal O(na^2) O(na2)。为啥呢?因为差分数组的总和只有 a a a,所以一定有大量的 0 0 0 存在。它们就不会提供转移复杂度了。所以,这个剪枝看上去是剪背包容量,实际上是剪物品个数。
……
(原文链接)
我们要做的是让所有数尽量靠近平均值,那么这个平均值就和上文的“分界点”有很大关联,由于差分数组的非零元素最多 600 个,所以可以除去非零元素后进行动态规划,时间复杂度 O ( n a 2 ) O(na^2) O(na2)。
太 低 级 了 !
接下来介绍一个 O ( n a ) O(na) O(na) 的可过做法,贪心+搜索。
由于平均值前面的差分值单减,后面的差分值单增,所以我们若知道了平均值,就可以从大到小枚举差分值,然后考虑往左还是往右填。
这里可以用贪心证明一个结论:每次一定先放入距离平均值更远的一边,前提是放完后不越过平均值线。
我们假设某时刻左边到平均值还有 L L L 的空间,右边还有 R R R 的空间,此时有两个差分值 a , b a,b a,b 要放入( b < a < L < R b<a<L<R b<a<L<R),那么比较 a a a 放两边的结果,就会发现填入右边更优。详细推导略。
我们会发现这个填数过程中, L L L 的小数部分、 R R R 的小数部分都不变,那么,若比较两数大小,到小数部分时的结果都是固定的。那么我们就可以优化枚举,不再枚举 n a na na 个平均数,而是枚举所有的 N N N, N + 1 3 N+\frac{1}{3} N+31 和 N + 2 3 N+\frac{2}{3} N+32( N ∈ { 0 , 1 , 2 , . . . , a m a x } N\in\{0,1,2,...,a_{max}\} N∈{0,1,2,...,amax}),一共 O ( a ) O(a) O(a) 个。
但是最终总得触及平均值线,怎么办?我们就暴力枚举最小的几位(两三个就够了)非零差分值该放哪边,这样并不影响 O ( n a ) O(na) O(na) 的时间复杂度。
CODE
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 10005
#define LL long long
#define ENDL putchar('\n')
#define DB double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
LL read() {
LL f = 1,x = 0;int s = getchar();
while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
void putnum(LL x) {
if(!x) {putchar('0');return ;}
if(x<0) putchar('-'),x = -x;
return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}
int n,m,s,o,k;
int a[MAXN],b[MAXN];
int main() {
freopen("variance.in","r",stdin);
freopen("variance.out","w",stdout);
n = read();
for(int i = 1;i <= n;i ++) {
a[i] = read();
if(i > 1) b[i-1] = a[i]-a[i-1];
}
sort(b + 1,b + n);
int cc = 0;
for(int i = 1;i < n;i ++) if(b[i] == 0) cc ++;
LL ans = 1e18;
for(int A = a[1]*3;A <= a[n]*3;A ++) {
for(int B = 0;B < 4;B ++) {
LL cna = a[1]+a[n],cn2 = a[1]*a[1] + a[n]*a[n];
DB av = A/3.0;
DB ct1 = av-a[1],ct2 = a[n]-av;
int c1 = a[1],c2 = a[n];
for(int i = n-1;i > cc+3;i --) {
if(ct1 > ct2) {
c1 += b[i]; ct1 -= b[i];
cna += c1; cn2 += c1*c1;
}
else {
c2 -= b[i]; ct2 -= b[i];
cna += c2; cn2 += c2*c2;
}
}
for(int i = min(n-1,cc+3);i > cc+1;i --) {
if(B & (1<<(i-cc-2))) {
c1 += b[i]; ct1 -= b[i];
cna += c1; cn2 += c1*c1;
}
else {
c2 -= b[i]; ct2 -= b[i];
cna += c2; cn2 += c2*c2;
}
}
for(int i = cc+1;i > 1;i --) {
if(ct1 > ct2) {
c1 += b[i]; ct1 -= b[i];
cna += c1; cn2 += c1*c1;
}
else {
c2 -= b[i]; ct2 -= b[i];
cna += c2; cn2 += c2*c2;
}
}
// printf("cna: %lld cn2: %lld\n",cna,cn2);
ans = min(ans,cn2*n - cna*cna);
}
}
AIput(ans,'\n');
return 0;
}