【题解】P2893 Making the Grade G
题目链接。
题意
给你一个长度为 \(n\) 的序列 \(a\)。
需要构造一个长度为 \(n\) 的非严格单调序列 \(b\),求出 \(\min_{i=1}^{n}\{|a_i-b_i|\}\)。
思路
感觉这题还挺难的。
首先我们需要证一个莫名其妙的东西,就是 \(b_i\in a(1\le i\le n)\)。
就是 \(b\) 中的元素一定在 \(a\) 中出现过。
在序列长度为 \(1\) 的时候,这个是一定满足的,毕竟取原值,答案就是 \(0\) 了啊。
然后递归证明(自己瞎起的)一下,就是从前 \(k-1\) 项满足这个定理,推导到前 \(k\) 项满足这个定理。
分讨一下。
首先是 \(b_{k-1}\le a_k\),这样直接取 \(a_k\) 就行了,这样 \(a_k\) 肯定是在 \(a\) 中的啊。
然后这个是大问题,当 \(b_{k-1}>a_k\) 时。
这样的话有两种办法,第一种是 \(b_k=b_{k-1}\),这样是满足条件的,因为 $b_{k-1} 在 \(a\) 中,所以 \(b_k\) 也在 \(a\) 中。
第二种是取一个 \(j\) 和 \(v\),让 \(b_{j\dots k}\) 都等于 \(v\),且 \(v>b_{j-1}\)。
我们要求的是啥?是 \(\min_{i=1}^{n}\{|a_i-b_i|\}\)。
而在 \(l\) 到 \(k\) 这一段区间内 \(b\) 的取值是相等的,这不就是“货库选址”吗,所以我们可以取 \(a_{l\dots k}\) 的中位数。但是如果这个中位数 \(p\le b_{l-1}\) 的话,我们的解就肯定是 \(b_{l-1}\) 了,这两种取值无论如何都是在 \(a\) 当中的。
证毕。
回归正题。
因为 \(b\) 可以是升的,也可以是降得,所以下面会讲述非严格单调单调上升,下降其实理解上升后改一改就行了,就不赘述了。
可见的是 \(n\) 的取值只有 \(2000\),所以我们可以考虑一下 dp。
我们可以设 \(f_{ij}\) 为 到了第 \(i\) 个数时,第 \(i\) 个数的取值为 \(j\),它的最小答案。
因为前面的引理,所以 \(j\) 只需要在序列 \(a\) 中便利,虽然 \(a\) 的值很大,但是我们可以离散化。
这样的话,转移还是很好搞得。
\(c\) 是离散化数组,因为离散化数组都是排好序的所以,上面的式子就是:
这样的话枚举 \(i\) 一个 \(O(n)\),枚举 \(j\) 一个 \(O(n)\),枚举 \(k\) 一个 \(O(n)\),总复杂度 \(O(n^3)\)。
这个方法在 luogu 上会 TLE 一个点,成为高贵的 \(90\) 分。
细细思考一下,当你扫描过一个答案 \(i\) 时,随着 \(j\) 的增加,\(k\) 的取值范围是不断增加,且 \(k\) 的取值范围是将之前的取值范围包含进去的。所以每经过一个 \(j\),我们就可以将它增加的 \(k\) 的取值的答案加入当前答案集合,这样的话 \(j+1\) 就不用循环 \(k\) 了,以此类推,\(k\) 的那一层循环就没了。
看一下代码理解一下吧。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 3e3 + 5;
const ll inf = 1000000000;
ll n;
ll a[N], b[N], c[N], tot;
ll f[N][N];
int main() {
cin >> n;
for (ll i = 1; i <= n; i++) cin >> a[i];
for (ll i = 1; i <= n; i++) b[i] = a[i];
sort(b + 1, b + 1 + n);
for (ll i = 1; i <= n; i++) {
if (b[i] != b[i - 1] || i == 1) {
c[++tot] = b[i];
}
}
for (ll i = 1; i <= n; i++) {
ll val = f[i - 1][1];
for (ll j = 1; j <= tot; j++) {
f[i][j] = abs(a[i] - c[j]) + val;
val = min(f[i - 1][j + 1], val);
}
}
ll an1 = inf;
for (ll i = 1; i <= tot; i++) an1 = min(f[n][i], an1);
for (ll i = 1; i <= n; i++) {
for (ll j = 1; j <= tot; j++) {
f[i][j] = 0;
}
}
for (ll i = 1; i <= n; i++) {
ll val = f[i - 1][tot]; // 没有改变初值,还写得上面的f[i-1][1]
for (ll j = tot; j >= 1; j--) {
f[i][j] = abs(a[i] - c[j]) + val;
val = min(f[i - 1][j - 1], val);
}
}
for (ll i = 1; i <= tot; i++) an1 = min(an1, f[n][i]);
cout << an1 << '\n';
return 0;
}
/*
15
1 8 5 10 3 12 9 6 15 7 11 2 4 13 14
2085
*/

浙公网安备 33010602011771号