CF1768F
只能说这题太妙了,场上 tourist 都没过。
题目描述
- 给定一个长度为
n 的序列a_1,a_2\dots,a_n 。 - 对于
1\le i\le j\le n ,你可以花费(i-j)^2\cdot\operatorname{min}(a_i,a_{i+1}\dots a_j) 的代价从i 跳到j 。 - 你现在要对于所有的
1\le k\le n ,输出从1 到k 的最段路。 -
解题思路
首先考虑
现在要考虑把这个 DP 优化到可以接受的时间复杂度内,考虑有以下性质。
性质1
对于
证明很简单,如果直接从
然而经过
显然因为对于
由此可知,每一步的区间最小值一定在两端。
性质2
对于一个最小值
证明:
对于一个步长
然而一步跳到的花费是
要满足一步跳到更优,就有
时间复杂度
有了以上性质我们就只要对于每个
对于
而对于
那么对于一个相同的
然而最多有
示例代码
代码非常好写。
#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';
}