Luckyleaves's Blog

stay hungry,stay foolish greedy and lucky

NOIP2021 方差

重考就我没改,退火还斜挂了,垫底了QWQ。

Solution

易得答案等于
$$
n \cdot \sum_{i=1}^n a_i^2-(\sum_{i=1}^na_i)^2
$$
然后不先推一波式子:
$$
\begin{aligned}
n \cdot \sum_{i=1}^n a_i^2-(\sum_{i=1}^na_i)^2 &= n \cdot \sum_{i=1}^n
(a_i-a_1)^2 - (\sum_{i=1}^n(a_i-a_1))^2 \\
&=n \cdot \sum_{i=1}^{n-1}(\sum_{j=1}^ib_j)^2-(\sum_{i=1}^{n-1}\sum_{j=1}^ib_j)^2 \\
\vdots\\
&= \sum_{i=1}^{n-1}\cdot b_i^2\cdot i\cdot(n-i)+2\sum_{j=1}^{n-1}\sum_{k=j+1}^{n-1}b_j\cdot b_k \cdot (n-j)\cdot k
\end{aligned}
$$
其中$b_i=a_{i+1}-a_i$

从最后一个式子可以看出最优解时差分数组呈现单谷(本人并没有看出来)。

附:对于这种结论,个人认为还是在考场上先写个程序将小数据的最优解的情况算出来,然后再用眼睛去蹬比较好。

然后得到差分数组单谷这个性质之后我们思考这个问题的解决方案,本人技艺不精,写了不够优秀的模拟退火,可以得到$84pts$,理论上(当然也有人确实做到了)可以拿到$96pts-100pts$。

但是仔细思考之后我们发现,完全可以乱搞。

当然,最简单的做法还是$dp$。

首先我们将差分数组排序,定义$f_{ij}$表示填了差分数组前$i$个数,其和为$j$的最优答案。

之后分将这个数放在左边还是放在右边两种情况讨论即可,不难得出转移方程式:
$$
f_{i,j+\sum_{k=1}^ib[k]}=\min f_{i-1,j}+(\sum_{k=1}^ib[i])^2\\
f_{i,j+b[i]\times i}=\min f_{i-1,j}+i \times(\sum_{k=1}^ib[i])^2+2\times j\times b[i]
$$
最上面的是放在右边的情况。下面是放在左边的情况。

然后我们会发现这个玩也儿是$O(n^2V)$,比较令人难受,但是我们观察最后一档得不到的分,此时的差分数组会出现大量的$0$,这意味着我们无论将这些东西放在左边还是放在右边都没有意义,于是将其特判掉(从有意义的开始转移)。

于是复杂度就是$O(\min(n,a)\times na)$了。

空间有点开不下,滚动一下数组。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<bits/stdc++.h>
#define PII pair<int, int>
#define LL long long
using namespace std;
template <class T>
inline void read(T &res)
{
res = 0; bool flag = 0;
char c = getchar();
while(c < '0' || '9' < c) {if(c == '-') flag = 1; c = getchar();}
while('0' <= c && c <= '9') res = (res << 3) + (res << 1) + (c ^ 48), c = getchar();
if(flag) res = -res;
}
template <class T, class ...ARC>
inline void read(T &res, ARC &...com){ read(res), read(com...); }
template <class T>
void write(T res)
{
if(res < 0) putchar('-'), res = -res;
if(res > 9) write(res / 10);
putchar(res % 10 + '0');
}
template <>
inline void write(char c) { putchar(c); }
template <>
inline void write(char *s) { while(*s) putchar(*s ++);}
template <class T, class ...ARC>
inline void write(T res, ARC ...com) { write(res), write(com...); }
const int N = 10005, M = 1e6 + 5;
int n;
int a[N], b[N], cnt, c[N];
LL f[2][M], sum, tot;
int main()
{
read(n);
for(int i = 1;i <= n;i ++) read(a[i]);
sum = n * a[n] * 2;
for(int i = 1;i < n;i ++) b[i] = a[i + 1] - a[i];
sort(b + 1, b + n);
for(int i = 1;i < n;i ++) c[i] += c[i - 1] + b[i];
for(int i = 1;i < n;i ++)
if(b[i] != 0) break;
else cnt ++;
memset(f[(cnt + 1) & 1], 0x3f, sizeof(f[(cnt + 1) & 1]));
f[(cnt + 1) & 1][0] = 0;
for(int i = cnt + 1, id;i < n;i ++)
{
memset(f[i & 1 ^ 1], 0x3f, sizeof(f[i & 1 ^ 1]));
for(int j = 0;j <= sum;j ++)
{
if(f[i & 1][j] > 1e18) continue;
f[i & 1 ^ 1][j + c[i]] = min(f[i & 1 ^ 1][j + c[i]], f[i & 1][j] + c[i] * c[i]); // right
f[i & 1 ^ 1][j + b[i] * i] = min(f[i & 1 ^ 1][j + b[i] * i], f[i & 1][j] + b[i] * b[i] * i + 2 * b[i] * j); // left
}
}
LL ans = 0x3f3f3f3f3f3f3f3f;
for(int i = 0;i <= sum;i ++)
if(f[n & 1][i] < 1e18) ans = min(ans, f[n & 1][i] * n - 1ll * i * i);
write(ans);
return 0;
}