🌓
Ftt2333's Nest

CF1768F

创建于 2022-11-21

Tags: OI

Categories: OI

只能说这题太妙了,场上 tourist 都没过。

题目描述

  • 给定一个长度为 nn 的序列 a1,a2,ana_1,a_2\dots,a_n
  • 对于 1ijn1\le i\le j\le n,你可以花费 (ij)2min(ai,ai+1aj)(i-j)^2\cdot\operatorname{min}(a_i,a_{i+1}\dots a_j) 的代价从 ii 跳到 jj
  • 你现在要对于所有的 1kn1\le k\le n,输出从 11kk 的最段路。
  • 1ain4×1051\le a_i\le n\le4\times10^5

解题思路

首先考虑 O(n2)O(n^2) 的 DP,fif_i 表示从 11ii 的最短路径,转移很简单。

现在要考虑把这个 DP 优化到可以接受的时间复杂度内,考虑有以下性质。

性质1

对于 1ijn1\le i\le j\le n,如果直接从 ii 跳到 jj,那么一定不会比经过区间最小值 aka_k 跳两次更优。

证明很简单,如果直接从 ii 跳到 jj,那么代价就是 ak(ij)2a_k\cdot(i-j)^2

然而经过 kk 的代价是 ak(ki)2+ak(jk)2=ak((ki)2+(jk)2)a_k\cdot(k-i)^2+a_k\cdot(j-k)^2=a_k\cdot((k-i)^2+(j-k)^2)

显然因为对于 a,b0a,b\ge0,有 (a+b)2a2+b2(a+b)^2\ge a^2+b^2,所以经过区间最小值更优。

由此可知,每一步的区间最小值一定在两端。

性质2

对于一个最小值 aia_i,一定满足他前面和后面的步长一定不会超过 n/ain/a_i

证明:

对于一个步长 dd,如果每次只跳 11 的距离,那么每次的花费都不会超过 nn,总花费不会超过 ndn\cdot d

然而一步跳到的花费是 aid2a_i\cdot d^2

要满足一步跳到更优,就有 aid2nda_i\cdot d^2\le n\cdot d,既 dnaid\le\frac{n}{a_i}

时间复杂度

有了以上性质我们就只要对于每个 ii,考虑以 aia_i 为最小值转移既可,可以证明这样的时间复杂度是 O(nn)O(n\sqrt n) 的。

对于 aina_i\ge\sqrt n,步长上限 d=naind=\frac n {a_i}\le\sqrt n,所以时间复杂度不会超过 O(nn)O(n\sqrt n)

而对于 ai<na_i<\sqrt n,考虑将将同的 aia_i 放在一起考虑。

那么对于一个相同的 aia_i,以他们为最小值的区间长度之和是 O(n)O(n)

然而最多有 n\sqrt n 种数,所以时间复杂度也不会超过 O(nn)O(n\sqrt n)

示例代码

代码非常好写。

#include <bits/stdc++.h>
using namespace std;
#ifndef LOCAL
void debug(...) {}
#endif
#define rep(i, n) for (int i = 0; i < (n); ++i)
#define per(i, n) for (int i = (n) - 1; ~i; --i)
#define all(v) (v).begin(), (v).end()
using ll = long long;
using vi = vector<int>;
 
int main() {
#ifndef LOCAL
  cin.tie(nullptr)->sync_with_stdio(false);
#endif
  constexpr ll inf = 1e18;
  int n;
  cin >> n;
  vi a(n);
  vector<ll> f(n, inf);
  rep(i ,n) cin >> a[i];
  f[0] = 0;
  rep(i, n) {
    int d = n / a[i];
    for (int j = i - 1; j >= 0 && i - j <= d; --j) {
      f[i] = min(f[i], f[j] + 1ll * (i - j) * (i - j) * a[i]);
      if (a[j] <= a[i]) break;
    }
    for (int j = i + 1; j < n && j - i <= d; ++j) {
      f[j] = min(f[j], f[i] + 1ll * (i - j) * (i - j) * a[i]);
      if (a[j] <= a[i]) break;
    }
  }
  rep(i, n) cout << f[i] << ' ';
  cout << '\n';
}

Powered by Hexo, theme by Facter