P4072 征途 Solution

斜率优化大水题。

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

m2×S2=m2×[1mi=1m(aia¯)2]=m2×(1mi=1mai22aia¯+a¯2)=mi=1mai22aia¯+a¯2

sum=i=1mai, sum=i=1mai2,则原式 =mi=1mai22ai(summ)+(summ)2=m×sum2sum2+sum2=m×sumsum2

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

得分 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;
}

接着考虑优化。重新看一遍题,发现最后的和一定为 ai,所以把枚举和的一维去掉,最后输出 m×dpn,m,ai(ai)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;
}

由于转移方程内含 ij 下标的乘积项,考虑斜率优化。

下面令 sumi=j=1iaj

首先,dpi,j=min{dpk,j1+(sumisumk)}

1k1<k2<in,且 k2 优于 k1

dpk2,j12sumi×sumk2+sumk22dpk1,j12sumi×sumk1+sumk12

i 有关的项移到左边 2sumi(sumk1sumk2)dpk1,jdpk2,j+sumk12sumk22

那么有 2sumi(dpk1,j1+sumk12)(dpk2,j1+sumk22)sumk1sumk2(除以负数要变号)。

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

#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 @   MistZero  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示