【问题描述】
FJ打算好好修一下农场中某条凹凸不平的土路。按奶牛们的要求,修好后的路面高度应当单调上升或单调下降,也就是说,高度上升与高度下降的路段不能同时出现在修好的路中。
整条路被分成了 N 段,N 个整数 A[1],…,A[N] 依次描述了每一段路的高度。FJ希望找到一个恰好含 N 个元素的不上升或不下降序列B[1],…,B[N],作为修过的路中每个路段的高度。
由于将每一段路垫高或挖低一个单位的花费相同,修路的总支出可以表示为:|A[1]-B[1]|+|A[2]-B[2]|+…+|A[N]-B[N]|。
请你计算一下,FJ在这项工程上的最小支出是多少。FJ向你保证,这个支出不会超过2^31-1。
【输入格式】
第1行:输入1个整数N;
第2..N+1行:第i+1行为1个整数A[i]
【输出格式】
第1行:输出1个正整数,表示FJ把路修成高度不上升或高度不下降的最小花费。
【输入样例】
7
1
3
2
4
5
3
9
【输出样例】
3
【样例解释】
FJ将第一个高度为3的路段的高度减少为2,将第二个高度为3的路段的高度增加到5,总花费为|2-3|+|5-3|=3,并且各路段的高度为一个不下降序列1,2,2,4,5,5,9。
【数据范围】
1<=N<=2,000
0<=A[i]<=1,000,000,000
题解:
先分析单调上升序列,首先想到的当然是最暴力的状态函数:
设f(i,j)表示第i个数修改为j时,前i个数满足条件的最小费用,则
f(i,j)=min{ f(i-1,k) | 0<=k<=j }+abs(a[i]-j)
显然这个状态转移方程在时间和空间上都不能满足题意,但至少答案是对的,可以过小数据,不过还需要进一步优化
进一步分析可以发现修改后的值一定是之前序列中出现过的值,证明如下:
对于子序列a[i],a[i+1],a[i+2],有a[i] < a[i+2],则
若a[i+1] > a[i+2],则a[i+1]修改为a[i+2]是最优的;
若a[i+1] < a[i],则a[i+1]修改为a[i]是最优的;
可以用归纳法证明整个序列
所以我们可以用b[i]表示原序列中第i小的数,则状态函数变为
f(i,j)表示第i个数修改为b[j]时,前i个数满足条件的最小费用,则
f(i,j)=min{ f(i-1,k) | 1<=k<=j }+abs(a[i]-b[j])
显然最终的答案ans=min{ f(n,k) | 1<=k<=j }
代码实现:
for(int i=2;i<=n;i++){
for(int j=1;j<=n;j++){
int tmp=inf;
for(int k=1;k<=j;k++) tmp=min(tmp,f[i-1][k]));
f[i][j]=tmp+abs(a[i]-b[j]);
}
}
三重循环,时间复杂度O(n^3),极限情况会超时,还需要进一步优化
可以观察到,在计算min{ f(i-1,k) | 1<=k<=j }时,前j-1个状态已经比较过了,所以我们用g(i,j)表示min{ f(i-1,k) | 1<=k<=j },在计算f(i,j)的同时维护g(i,j)
代码实现:
for(int i=2;i<=n;i++){
for(int j=1;j<=n;j++){
f[i][j]=g[i-1][j]+abs(a[i]-b[j]),g[i][j]=min(g[i][j-1],f[i][j]);
}
}
边界条件:
f(1,i)=abs(a[1]-b[i]),g(1,i)=min(g(1,i-1),f(1,i));
单调下降序列同理
完整代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=2005;
int m,n,a[maxn],b[maxn],ans1,ans2;
int f[maxn][maxn],g[maxn][maxn];
inline int in(){
int x=0,f=1;char ch=getchar();
while(ch<'0'|ch>'9'){if(ch=='-') f=-f;ch=getchar();}
while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();
return x*f;
}
bool cmp1(int i,int j){
return i>j;
}
int dp(){
for(int i=1;i<=n;i++)
f[1][i]=abs(a[1]-b[i]),g[1][i]=min(g[1][i-1],f[1][i]);
for(int i=2;i<=n;i++)
for(int j=1;j<=n;j++)
f[i][j]=g[i-1][j]+abs(a[i]-b[j]),g[i][j]=min(g[i][j-1],f[i][j]);
return g[n][n];
}
int main(){
n=in();
for(int i=1;i<=n;i++) a[i]=b[i]=in();
sort(b+1,b+n+1);
memset(f,127,sizeof(f)); //初始化为inf
ans1=dp();
sort(b+1,b+n+1,cmp);
memset(f,127,sizeof(f));
ans2=dp();
printf("%d\n",min(ans1,ans2));
return 0;
}