「SDOI2016」征途

原题面:LojLuogu

斜率优化,\(k_i\)\(x_i\) 均单调,单调队列。

王 道 征 途
「そうですね…やっぱり僕は、王道を征く、ソープ系ですか」

简述

给定一列有序的 \(n\) 个物品,第 \(i\) 个物品的价值为 \(a_i\)
给定参数 \(m\),要求将一列物品分成 \(m\) 段,最小化每段长度之和的方差 \(v\),输出 \(v\cdot m^2\)
\(1\le n\le m\le 3000\)\(\left(\sum a_i\right)\le 3\times 10^4\)
1S,256MB。

分析

记划分出的 \(m\) 段的和分别为 \(b_1\sim b_m\),将 \(v\cdot m^2\) 展开一下:

\[\begin{aligned} v\cdot m^2 =& m^2\cdot \dfrac{\sum\limits_{i=1}^{m}\left(b_i - \overline{b}\right)^2}{m}\\ =& m\sum_{i=1}^{m} \left( b_i^2 - 2b_i\overline{b} + \overline{b}^2 \right)\\ =& m\sum_{i=1}^{m} \left( b_i^2 - 2b_i\dfrac{\sum_{i=1}^m b_i}{m} + \dfrac{\left(\sum_{i=1}^m b_i\right)^2}{m^2} \right)\\ =& m\sum_{i=1}^{m} b_i^2 - 2\left(\sum_{i=1}^m{b_i}\right)^2 + \left(\sum_{i=1}^m{b_i}\right)\\ =& m\sum_{i=1}^{m} b_i^2 - \left(\sum_{i=1}^{m}{b_i}\right)^2 \end{aligned}\]

发现最后的式子中 \(\left(\sum{b_i}\right)^2\) 等于 \(\left(\sum{a_i}\right)^2\),是一个定值,仅需最小化第一项即可。

按传统先写个暴力,设 \(f_{i,j}\) 表示将前 \(i\) 个数划分为 \(j\) 段时 \(\sum b_i^2\) 的最小值,转移时枚举段数 \(k\) 和上一段的结尾 \(j\),则有:

\[f_{i,k} = \min_{j=0}^{i-1} \left\{ f_{j,k-1} + \left(\sum_{l=j+1}^{i}a_l\right)^2\right\} \]

预处理前缀和后暴力转移复杂度 \(O(n^2m)\),考虑优化。
发现后一项是一个区间和的平方的形式,它同时与 \(i,j\) 有关。考虑对 \(a\) 做一个前缀和,设 \(s_i = \sum_{i=1}^{i} a_i\),将其代入原式:

\[\begin{aligned} f_{i,k} &= \min_{j=0}^{i-1} \left\{ f_{j,k-1} + \left( s_i - s_j \right)^2 \right\}\\ f_{i,k} &= \min_{j=0}^{i-1} \left\{ f_{j,k-1} + s_i^2 - 2s_is_j +s_j^2 \right\} \end{aligned}\]

考虑先通过枚举固定 \(k\),对于每一个决策 \(f_{j,k}\),都有:

\[f_{i,k} - s_{j}^2= \left(f_{j,k - 1} + s_j^2\right) - 2s_is_j \]

这是一个显然的斜率优化的形式,套路地设:

\[\begin{aligned} x_i &= s_i\\ y_i &= f_{i,k-1} + s_i^2\\ k_i &= 2s_i\\ b_i &= f_{i,k} - s_i^2 \end{aligned}\]

对于上式,显然应最小化截距 \(b_i\) 的值,易证最优决策点一定位于下凸包上。又 \(x_j\)\(k_i\) 均随 \(i\) 的增加而增加,单调队列维护下凸包即可。注意一些初始化的小问题,详见代码。
总复杂度 \(O(nm)\) 级别。

代码

//知识点:斜率优化
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
#define LD long double
const int kN = 3000 + 10;
//=============================================================
int n, m, h = 1, t, q[kN];
LL s[kN], f[kN][kN]; //代码中 f[j][i] 表示将前 i 个数划分为 j 段时 sum b_i^2 的最小值,与上述分析中相反。
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmin(LL &fir, LL sec) {
  if (sec < fir) fir = sec;
}
LD X(int x_) {
  return s[x_];
}
LD Y(int id_, int x_) {
  return f[id_][x_] + s[x_] * s[x_];
}
LD K(int id_, int x_, int y_) {
  if (X(x_) == X(y_)) return (Y(id_, y_) > Y(id_, x_) ? 1e18 : -1e18);
  return (LD) ((Y(id_, y_) - Y(id_, x_)) / (X(y_) - X(x_)));
}
int Query(int id_, int now_) {
  LD know = 2 * s[now_];
  while (h < t && K(id_, q[h], q[h + 1]) <= know) ++ h;
  return q[h];
}
void Insert(int id_, int now_) {
  while (h < t && K(id_, q[t - 1], q[t]) >= K(id_, q[t - 1], now_)) -- t;
  q[++ t] = now_;
}
//=============================================================
int main() { 
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) s[i] = s[i - 1] + read();
  memset(f, 63, sizeof(f));
  f[0][0] = 0;
  for (int k = 1; k <= m; ++ k) {
    h = 1, t = 0;
    Insert(k - 1, 0);
    for (int i = 1; i <= n; ++ i) {
      if (i >= k) {
        int j = Query(k - 1, i);
        f[k][i] = f[k - 1][j] + (s[i] - s[j]) * (s[i] - s[j]);
      }
      Insert(k - 1, i);
    }
  }
  printf("%lld\n", 1ll * m * f[m][n] - 1ll * s[n] * s[n]);
  return 0; 
}
posted @ 2021-02-01 06:23  Luckyblock  阅读(102)  评论(0编辑  收藏  举报