https://www.acwing.com/problem/content/275/
题意
给定一个序列 A A A,要求构造一个序列 B B B使得
- B B B非严格单调,也就是允许取等的上升或下降序列
- 最小化
S
=
∑
i
=
1
N
∣
A
i
−
B
i
∣
S=\sum_{i=1}^{N}|A_i-B_i|
S=∑i=1N∣Ai−Bi∣
输出S
思路
该问题具有这样一条性质:
B必然可由A中元素构成得到最优解
我们假设现在已有最优解 B B B,但 B B B中元素尚不是由 A A A中元素构成。则我们任取其中位于 A i A_i Ai和 A i + 1 A_{i+1} Ai+1之间的一些元素,并记 B B B中小于等于 A i A_i Ai的元素有 X X X个,大于等于 A i + 1 A_{i+1} Ai+1的元素有 Y Y Y个。则
- 当 X < Y X<Y X<Y时,必然可以通过将选取元素中最大者调整为 A i + 1 A_{i+1} Ai+1的方式得到更优结果。
- 当 X > Y X>Y X>Y时,必然可以通过将选取元素中最小者调整为 A i A_{i} Ai的方式得到更优结果。
- 当 X = Y X=Y X=Y时,可以任意向上或向下调整,结果不会变差。
由此,我们知道可以通过取A中的元素并重新排列得到B的最优解。
接下来,我们设计状态转移方程部分
f[i][j]
代表给A[1]~A[i]
分配好了值且最后一个数是A'[j]
(第j大的数)的方案的集合,值是集合中所有方案的最小值(最优)。
从倒数第二个分配的数转移,有
倒数第二个数选取的是A'[1]
,则最小值是f[i-1][1]+abs(A[i]-A'[1])
倒数第二个数选取的是A'[2]
,则最小值是f[i-1][2]+abs(A[i]-A'[2])
以此类推,最后取最小值即可。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2010;
const int INF=0x3f3f3f3f;
typedef long long ll;
ll a[N],b[N],f[N][N];
int n;
ll work(){
memcpy(b, a, sizeof b);
sort(b+1,b+n+1);
for(int i=1;i<=n;i++){
ll minv=INF;
for(int j=1;j<=n;j++){
minv=min(minv,f[i-1][j]);
f[i][j]=minv+abs(a[i]-b[j]);
}
}
ll res=INF;
for(int i=1;i<=n;i++) res=min(res,f[n][i]);
return res;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
ll res=work();
reverse(a+1,a+n+1);
res=min(res,work());
cout<<res<<endl;
return 0;
}