P4072 征途 Solution

斜率优化大水题。

首先看到方差,想到拆式子。(下面 \(a_i\) 表示每一段路程的长度)

\(m^2 \times S^2=m^2 \times [\frac{1}{m}\sum\limits_{i=1}^m (a_i-\overline a)^2]=m^2 \times (\frac{1}{m}\sum\limits_{i=1}^ma_i^2-2a_i\overline a+\overline a^2)=m\sum\limits_{i=1}^ma_i^2-2a_i\overline a+\overline a^2\)

\(sum=\sum\limits_{i=1}^m a_i,\space sum'=\sum\limits_{i=1}^ma_i^2\),则原式 \(=m\sum\limits_{i=1}^ma_i^2-2a_i(\dfrac{sum}{m})+(\dfrac{sum}{m})^2=m\times sum'-2sum^2+sum^2=m \times sum'-sum^2\)

考虑像方差这题的处理方式,将和压入状态,将平方和作为状态表示的答案,即设 \(dp_{i,j,k}\) 表示当前枚举到第 \(i\) 段路径的右端点,为第 \(j\) 次停靠,当前的路径和为 \(k\) 的最小平方和,最后输出 \(\min\{m \times dp_{n,m,k}-k^2\}\) 即可,复杂度为 \(O(N^2M\sum a_i)\),空间巨大。(其实这是看错题的写法)

得分 \(50pts\)

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

const int N = 110;
const int M = 2e3 + 10;

int n, m, Max, a[N], sum[N], dp[N][N][M];

int main() {
  ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
  cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i], sum[i] = sum[i - 1] + a[i];
  memset(dp, 0x3f, sizeof(dp)); dp[1][0][0] = 0; Max = sum[n];
  for (int i = 2; i <= n + 1; ++i) {
    for (int j = 1; j <= m; ++j) {
      for (int k = 1; k < i; ++k) {
        int tmp = sum[i - 1] - sum[k - 1];
        for (int p = Max; p >= tmp; --p) {
          dp[i][j][p] = min(dp[i][j][p], dp[k][j - 1][p - tmp] + tmp * tmp);
        }
      }
    }
  }
  int res = dp[0][0][0];
  for (int p = 0; p <= Max; ++p) {
    if (dp[n + 1][m][p] == dp[0][0][0]) continue;
    res = min(res, dp[n + 1][m][p] * m - p * p);
  }
  cout << res << endl;
  return 0;
}

接着考虑优化。重新看一遍题,发现最后的和一定为 \(\sum a_i\),所以把枚举和的一维去掉,最后输出 \(m \times dp_{n,m,\sum a_i} - (\sum a_i)^2\) 即可。

得分 \(90pts\)

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

const int N = 3e3 + 10;
int n, m, Max, a[N], sum[N], dp[N][N];

int main() {
  ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
  cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i], sum[i + 1] = sum[i] + a[i];
  memset(dp, 0x3f, sizeof(dp)); dp[1][0] = 0; Max = sum[n + 1];
  for (int i = 2; i <= n + 1; ++i) {
    for (int j = 1; j <= m; ++j) {
      for (int k = 1; k < i; ++k) {
        int tmp = sum[i] - sum[k];
        dp[i][j] = min(dp[i][j], dp[k][j - 1] + tmp * tmp);
      }
    }
  }
  cout << dp[n + 1][m] * m - Max * Max << endl;
  return 0;
}

由于转移方程内含 \(i\)\(j\) 下标的乘积项,考虑斜率优化。

下面令 \(sum_i=\sum\limits_{j=1}^i a_j\)

首先,\(dp_{i,j}=\min\{dp_{k,j-1}+(sum_i-sum_k)\}\)

\(1 \le k_1 < k_2 < i \le n\),且 \(k_2\) 优于 \(k_1\)

\(dp_{k_2,j-1}-2sum_i \times sum_{k_2}+sum_{k_2}^2 \le dp_{k_1,j-1}-2sum_i \times sum_{k_1}+sum_{k_1}^2\)

\(i\) 有关的项移到左边 \(2sum_i(sum_{k_1}-sum_{k_2})\le dp_{k_1,j}-dp_{k_2,j}+sum_{k_1}^2-sum_{k_2}^2\)

那么有 \(2sum_i \ge \dfrac{(dp_{k_1,j-1}+sum_{k_1}^2)-(dp_{k_2,j-1}+sum_{k_2}^2)}{sum_{k_1}-sum_{k_2}}\)(除以负数要变号)。

维护 \(m\) 个单调队列就可以了,第 \(j\) 层的某个状态从 \(j-1\) 层转移而来即可。

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

const int N = 3e3 + 10;
int n, m, Max, a[N], l[N], r[N], sum[N], q[N][N], dp[N][N];
inline double slope(int j, int k1, int k2) {
  return 1. * ((dp[k2][j] + sum[k2] * sum[k2]) - (dp[k1][j] + sum[k1] * sum[k1])) / (sum[k2] - sum[k1]);
}

signed main() {
  ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
  cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i], sum[i] = sum[i - 1] + a[i];
  Max = sum[n]; for (int i = 0; i <= m; ++i) l[i] = r[i] = 1;
  for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= m; ++j) {
      while (l[j - 1] < r[j - 1] && 2 * sum[i] >= slope(j - 1, q[j - 1][l[j - 1]], q[j - 1][l[j - 1] + 1])) ++l[j - 1];
      dp[i][j] = dp[q[j - 1][l[j - 1]]][j - 1] + (sum[i] - sum[q[j - 1][l[j - 1]]]) * (sum[i] - sum[q[j - 1][l[j - 1]]]);
      while (l[j] < r[j] && slope(j, q[j][r[j]], i) <= slope(j, q[j][r[j] - 1], q[j][r[j]])) --r[j];
      q[j][++r[j]] = i;
    }
  }
  cout << dp[n][m] * m - Max * Max << endl;
  return 0;
}
posted @ 2022-10-27 14:19  MistZero  阅读(21)  评论(0编辑  收藏  举报