P7962 方差 Sol

P7962 方差:DP 好题。

首先可以观察到,将 \(a_i\) 赋为 \(a_{i-1}+a_{i+1}-a_i\) 等同于交换 \(a_i,a_{i+1}\) 的差分数组。

下面令差分数组 \(d_i=a_{i+1}-a_i\)

那么每次操作相当于交换 \(d_{i-1},d_i\),不妨推广一下,题目等同于重排 \(d\) 后求最小方差。

首先求方差的式子可以转化为 \(n\sum\limits_{i=1}^n a_i^2 - (\sum\limits_{i=1}^n a_i)^2\)

也就是说我们需要通过方差数组求出全局和、平方和。

由于这两个数不太好同时转移,所以考虑设定一维成为状态。

接着考虑策略。考虑到方差本质上是反映一组数的波动情况,那么在差不变的情况下,显然要使得尽量多的数往中间靠。

那么想到对于 \(d\) 小的,可以尽量放在中间,使得差尽量小,更多的数靠近平均数,随后向左、向右严格非减。

所以做法是把 \(d\) 直接重排,每一次考虑放在原数组的左边 / 右边,然后计算 \(\Delta\),即改变的贡献。

式子就很好推了。

令当前状态 \(f(i,s)\) 表示放了 \(i-1\) 个方差,原数组总和为 \(s\)

放左边的情况其实就是往右边所有放好的数加上一个差分 \(d_i\)

为什么是对的?方差和数本身无关,而与相对差有关。

在相对差不变的情况下,我们可以任意钦定 \(a_1\),这里默认 \(a_1\)\(0\),则右边所有数 \(+d_i\)

放左边:\(f(i+1,s+i\times d_i)=\max\{f(i,s)+ \sum\limits_{j=1}^i (2a_jd_i+d_i^2)\}\)

化简得到 \(f(i+1,s+i\times d_i)=\max\{f(i,s)+ s \times d_i+i \times d_i^2\}\)

如果放右边,就不会对右边的数产生贡献,那么原数显然为 \(\sum\limits_{j=1}^i d_j\)

放右边:\(f(i+1,s+\sum\limits_{j=1}^i d_j)=\max\{f(i,s)+(\sum\limits_{j=1}^i d_j)^2\}\)

这里的 \(\sum d\) 可以通过预处理得到。

那么这样枚举和的数量就很大了。

考虑到值域很小,所以有很多差分为 \(0\),可以忽略贡献。

所以复杂度可以大大优化,对于 \(d\) 不同的转移即可。

最后输出 \(\max\{f(n,i)-i^2\}\) 即可。

#include <bits/stdc++.h>
#define int long long
using namespace std;

template <typename T> inline void read(T &x) {
  x = 0; char ch = getchar();
  while (!isdigit(ch)) ch = getchar();
  while (isdigit(ch)) x = (x<<1) + (x<<3) + (ch^48), ch = getchar();
}

const int N = 1e4 + 10;
const int inf = 0x3f3f3f3f3f3f3f3f;
int n, res = inf, a[N], cf[N], sum[N], dp[2][N<<6];
inline void Min(int &x, int y) { x = min(x, y); }

signed main() {
  read(n);
  for (int i = 1; i <= n; ++i)
    read(a[i]), cf[i] = a[i] - a[i - 1];
  cf[1] = inf; sort(cf + 1, cf + 1 + n);
  int num = upper_bound(cf + 1, cf + n, 0) - cf - 1;
  for (int i = 1; i < n; ++i) sum[i] = sum[i - 1] + cf[i];
  memset(dp[num & 1], 0x3f, sizeof dp[num & 1]);
  dp[num & 1][0] = 0;
  for (int i = num + 1; i < n; ++i) {
    memset(dp[i & 1], 0x3f, sizeof dp[i & 1]);
    for (int j = 0; j <= a[n] * n; ++j) {
      if (dp[1 - (i & 1)][j] == inf) continue;
      Min(dp[i & 1][j + sum[i]], dp[1 - (i & 1)][j] + sum[i] * sum[i]);
      Min(dp[i & 1][j + i * cf[i]], dp[1 - (i & 1)][j] + cf[i] * (cf[i] * i + 2 * j));
    }
  }
  for (int i = 0; i <= a[n] * n; ++i) {
    if (dp[1 - (n & 1)][i] == inf) continue;
    res = min(res, dp[1 - (n & 1)][i] * n - i * i);
  }
  cout << res << endl;
  return 0;
}
posted @ 2022-10-20 08:02  MistZero  阅读(16)  评论(0编辑  收藏  举报