P7962 [NOIP2021] 方差

P7962 [NOIP2021] 方差

给定长度为 \(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\)

对于所有的数据,保证 \(1 \le n \le {10}^4\)\(1 \le a_i \le 600\)

答案化简为:

\[n \cdot \sum_{i=1}^n a_i^2 - \left(\sum_{i=1}^n a_i\right)^2\]

看一组数 \(a_1, a_2, \dots, a_n\),对第 \(k\) 个数操作后变成 \(a'_k\),而两组数的差分分别为 \(d_1, d_2, \dots, d_{n-1}\)\(d'_1, d'_2, \dots, d'_{n-1}\)

于是容易证明,该操作对数列的影响就是交换差分。

\(d_i = a_{i+1} - a_i\),对答案做些变换:

\[ \begin{aligned} n \cdot \sum_{i=1}^n a_i^2 - \left(\sum_{i=1}^n a_i\right)^2 &= n \cdot \sum_{i=1}^n (a_i - a_1)^2 - \left(\sum_{i=1}^n (a_i - a_1)\right)^2 \\ &= n \cdot \sum_{i=1}^{n-1} \left(\sum_{j=1}^i d_j\right)^2 - \left(\sum_{i=1}^{n-1} (n - i) d_i\right)^2 \\ &= n \cdot \sum_{j=1}^{n-1} \sum_{k=1}^{n-1} d_j \cdot d_k \cdot (n - \max\{j, k\}) - \sum_{j=1}^{n-1} \sum_{k=1}^{n-1} d_j \cdot d_k \cdot (n - j)(n - k) \\ &= \sum_{j=1}^{n-1} \sum_{k=1}^{n-1} d_j \cdot d_k \cdot (n \cdot \min\{j, k\} - j \cdot k) \\ &= \sum_{i=1}^{n-1} d_i^2 \cdot i \cdot (n - i) + 2 \sum_{1 \leq j < k \leq n-1} d_j \cdot d_k \cdot (n - j) \cdot k \end{aligned} \]

通过看最后一个式子可以看出答案最小时差分应该是呈现单谷,即答案最小时差分值先减后增。

所以可以考虑将所有差分从小到大排序后,然后设计一个动态规划决策每个差分值该放在单谷的左边还是右边。

转化过后的式子不好直接处理,还是用原先的式子较容易推导。

\(f(i, x)\) 表示已经考虑完前 \(i\) 个差分值,此时的 \(\sum_{j=1}^i (n - j) d_j\) 的和为 \(x\) 时最小的平方和,设 \(s_i = \sum_{j=1}^i d_j\)

则现在要考虑 \(d_{i+1}\) 放在单谷的左边还是单谷的右边:

  • 放左边
\[f(i, x) + 2 \cdot x \cdot d_{i+1} + i \cdot d_{i+1}^2 \rightarrow f(i+1, x + i \cdot d_{i+1})\]
  • 放右边
\[f(i, x) + s_{i+1}^2 \rightarrow f(i+1, x + s_{i+1})\]

边界条件:

\[f(1, 0) = 0, \quad f(i, x) = +\infty \quad ((i, x) \neq (1, 0))\]

答案为:

\[\text{ans} = \min_{x} \left\{n \cdot f(n, x) - x^2\right\}\]

其中 \(x\)\(\sum_{j=1}^{n-1} (n - j) d_j\) 中得到的最大的和。

考虑到所有差分值 \(d_i\) 非负,这个动态规划可以用类似背包的方法把第一维空间给优化掉,而第二维的范围是 \(O(n^2 \cdot a_i)\),所以空间可以承受。

时间复杂度为 \(O(n^2 \cdot a_i)\),通过优化后变为 \(O(n \cdot a_i)\),可以通过。

注意要开 long long

实现
 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
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
const int MAXX = 5e5 + 10, MAXN = 1e5 + 10;
const ll inf = 1e12;
inline void chkmx(int &a, int b) { a = a < b ? b : a; }
inline void chkmn(int &a, int b) { a = a > b ? b : a; }
inline void chkmx(ll &a, ll b) { a = a < b ? b : a; }
inline void chkmn(ll &a, ll b) { a = a > b ? b : a; }
ll a[MAXN], d[MAXN], s[MAXN], f[MAXX], n, mx, mxa, ans = inf;
int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i], d[i - 1] = a[i] - a[i - 1], chkmx(mxa, a[i]);
    for (int i = 1; i <= mxa * n; i++)
        f[i] = inf;
    f[0] = s[0] = 0;
    sort(d + 1, d + n);
    for (int i = 1; i <= n; i++)
    {
        s[i] = s[i - 1] + d[i];
        if (!d[i])
            continue;
        for (int j = mx; j >= 0; --j)
        {
            if (f[j] == inf)
                continue;
            chkmn(f[j + i * d[i]], f[j] + 2 * j * d[i] + i * d[i] * d[i]);
            chkmn(f[j + s[i]], f[j] + s[i] * s[i]);
            mx = max(mx, max(j + i * d[i], j + s[i]));
            f[j] = inf;
        }
    }
    for (int i = 0; i <= mx; i++)
        if (f[i] < inf)
            chkmn(ans, n * f[i] - 1ll * i * i);
    cout << ans;
    return 0;
}