题目
题目提交点: 点我进入ACwing官方提交此题
给定长度为 N 的序列 A,构造一个长度为 N 的序列 B,满足:
B 非严格单调,即 B1≤B2≤…≤BN 或 B1≥B2≥…≥BN。
最小化 S=∑Ni=1|Ai−Bi|。
只需要求出这个最小值 S。
输入格式
第一行包含一个整数 N。
接下来 N 行,每行包含一个整数 Ai。
输出格式
输出一个整数,表示最小 S 值。
数据范围
1≤N≤2000,
0≤Ai≤109
输入样例:
7
1
3
2
4
5
3
9
输出样例:
3
题目思路
为了能够构造出最小的B序列,我们可以想到,先挑出最长上升子序列,这样的话,这些最长上升子序列放到对应的地方所产生的值是为零。然后我们再去补值:
因为要保持上升,所以这两个位置需要这么放最好。
我们再看一个
这个想法还是很多缺陷,只是带领大家往这个方向想,严谨的证明方法可以采用微调法证明。
那么我们就可以推断,在满足最小化的情况下,一定存在一种构造序列B的方案,使得B中的值都在A中出现过。
- DP分析
集合表示:
前
i
个
数
已
经
构
造
完
成
,
且
最
后
一
个
数
是
B
j
前i个数已经构造完成,且最后一个数是B_j
前i个数已经构造完成,且最后一个数是Bj(因为要枚举到选
B
j
B_j
Bj的情况)
属性:
m
i
n
min
min
那么状态划分就要以倒数第二个数来进行划分。
状态转移方程为:
f
[
i
,
j
]
=
m
i
n
(
f
[
i
,
j
]
,
f
[
i
−
1
,
k
]
+
∣
A
i
−
B
j
∣
)
f[i, j] = min(f[i,j], f[i - 1, k] + |A_i - B_j|)
f[i,j]=min(f[i,j],f[i−1,k]+∣Ai−Bj∣)
但是这样就有三重循环,我们需要减少一重循环才不会超时,如何优化呢?
我们可以发现,
f
[
i
,
j
−
1
]
f[i, j - 1]
f[i,j−1]的状态转移方程只是比上面那个方程少一个k = j而已,其他都是重复的,所以就可以用一个值来代替。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define debug(a) cout << #a << " = " << a << endl;
#define mod(x) (x) % MOD
#define ENDL "\n"
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 2000 + 10, INF = 0x3f3f3f3f;
int f[N][N], a[N], n, b[N];
int dp() {
memcpy(b, a, sizeof a);
sort(b + 1, b + n + 1);
int ans = INF;
_rep(i, 1, n) {
int minv = INF;
_rep(j, 1, n) {
minv = min(minv, f[i - 1][j]);
f[i][j] = minv + abs(a[i] - b[j]);
if (i == n) ans = min(ans, f[i][j]);
}
}
return ans;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false); // 取消cin与stdin 的同步
cout.tie(0), cin.tie(0);
cin >> n;
_rep(i, 1, n) cin >> a[i];
int ans = dp();
reverse(a + 1, a + n + 1);
cout << min(ans, dp()) << ENDL;
return 0;
}