AcWing 273. 分级
题目
给一个序列 A,长度 n < 3e3。你需要构造出序列 B,且序列 B只能不增或者不降。求 S 的最小值。 S = ∑ i = 1 N ∣ A i − B i ∣ S=\sum_{i=1}^{N}\left|A_{i}-B_{i}\right| S=i=1∑N∣Ai−Bi∣
分析
首先不增或者不降的序列做法都一样,只需要反转数组再做一遍求最小值即可。先假设求不降的序列。
题目关键(性质):一定存在一组最优解 B,B 序列的每一个元素都在 A 序列中出现过。
其实自己画几个样例就能发现。
证明
先求出排序过的
A
′
A'
A′
①: 状态表示(经验)
- 集合: d p [ i ] [ j ] dp[i][j] dp[i][j] 表示所有给 A [ 1... i ] A[1...i] A[1...i]分配好,且最后一位是 A ′ [ j ] A'[j] A′[j] 的所有方案集合
- 属性:表示集合中所有方案求得结果的最小值
②: 状态转移
因为最后一位已经确定,根据倒数第二位来划分集合 d p [ i ] [ j ] dp[i][j] dp[i][j](继续划分),枚举 k k k ( k <= j) ----> d p [ i − 1 ] [ k ] + ( b [ j ] − a [ i ] ) dp[i-1][k] + (b[j] - a[i]) dp[i−1][k]+(b[j]−a[i])
复杂度 O ( n 3 ) O(n3) O(n3)
当然,这里的 k k k 还是可以通过前缀最大值优化,变成 O ( n 2 ) O(n2) O(n2)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f;
const int N = 2e3 + 5;
int n, a[N], b[N];
int dp[N][N];
int DP() {
for (int i = 1; i <= n; i++)
b[i] = a[i];
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; i++) {
int minv = INF;
for (int j = 1; j <= n; j++) {
minv = min(minv, dp[i-1][j]); // 记录前缀最值
dp[i][j] = minv + abs(b[j] - a[i]);
}
}
int ans = INF;
for (int i = 1; i <= n; i++)
ans = min(ans, dp[n][i]);
return ans;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int res = DP();
reverse(a + 1, a + n + 1);
res = min(res, DP()); // 求两次取最小
printf("%d\n", res);
return 0;
}