【bzoj1592】【Usaco2008 Feb】【动态规划】CQYZ_Vijos_P1468 路面修整

【问题描述】

  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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值