路面修整的题解

FJ 打算好好修一下农场中某条凹凸不平的土路。按奶牛们的要求,修好后的
路面高度应当单调上升或单调下降,也就是说,高度上升与高度下降的路段不能
同时出现在修好的路中。
整条路被分成了 N 段,N 个整数 A_1, … , A_N (1 <= N <= 2,000)依次描述
了每一段路的高度(0 <= A_i <= 1,000,000,000)。FJ 希望找到一个恰好含 N 个
元素的不上升或不下降序列 B_1, … , B_N,作为修过的路中每个路段的高度。
由于将每一段路垫高或挖低一个单位的花费相同,修路的总支出可以表示为:
|A_1 - B_1| + |A_2 - B_2| + … + |A_N - B_N|
请你计算一下,FJ 在这项工程上的最小支出是多少。FJ 向你保证,这个支出
不会超过 2^31-1。

重点来了!!!敲黑板!!!

首先,我们需要一个性质。这个性质是最终的所有数都源自于原来的数列。

为什么?首先,我们证明一下。

我们从 33 个数开始想,假设这 33 个数是 a,b,ca,b,c

  1. a<b&b<c&a<ca<b And b<c And a<c
  • 这种情况,比方说 7 8 9,之后,我们的方案是 7 8 9 是最优方案之一。
  1. a>b&b>c&a>ca>b And b>c And a>c
  • 这种情况,比方说 7 6 5,之后,我们的方案是 7 6 5 是最优方案之一。
  1. a<b&b>c&a<ca<b And b>c And a<c
  • 这种情况,比方说 1 9 7,之后,我们的方案是 1 7 9 是最优方案之一。
  1. a<b&b>c&a>ca<b And b>c And a>c
  • 这种情况,比方说 8 9 7,之后,我们的方案是 9 9 7 是最优方案之一。
  1. a>b&b<c&a>ca>b And b<c And a>c
  • 这种情况,比方说 7 5 9,之后,我们的方案是 5 5 9 是最优方案之一。
  1. a>b&b<c&a<ca>b And b<c And a<c
  • 这种情况,比方说 7 5 6,之后,我们的方案是 7 5 5 是最优方案之一。

之后,我们考虑给数列加一个数 dd。我们刚才已经证明了,a,b,ca,b,c 3个数,任意排列都是最优的。现在,我们来分类讨论。

为了方便,假设都是第 11 种情况,a<b<ca < b < c,最优排列是 a,b,ca,b,c

  • dad leq a

这种情况,显然,对不对。比如 a,b,ca,b,c 的最优排列是 3,5,7,93,5,7,9d=2d=2。我觉得不用解释了。

  • da&dbdgeq a And dleq b

这种情况,显然,对不对。比如 a,b,ca,b,c 的最优排列是 3,5,7,93,5,7,9d=4d=4。我觉得不用解释了。

  • db&dcdgeq b And dleq c

这种情况,显然,对不对。比如 a,b,ca,b,c 的最优排列是 3,5,7,93,5,7,9d=6d=6。我觉得不用解释了。

  • dcdgeq c

这种情况,显然,对不对。比如 a,b,ca,b,c 的最优排列是 3,5,7,93,5,7,9d=10d=10。我觉得不用解释了。

竟然打了 44 个显然,只不过确实显然。

然后,如果您就可以感性理解一下,我们继续加数,肯定依然是最优。

这样代码就很好写了。

大概这样:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
    T RR=1;FF=0;char CH=getchar();
    for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
    for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
    FF*=RR;
}
template<typename T>inline void write(T x){
    if(x<0)putchar('-'),x*=-1;
    if(x>9)write(x/10);
    putchar(x%10+48);
}
template<typename T>inline void writen(T x){
    write(x);
    puts("");
}
const int MAXN=2e3+10;
int n,a[MAXN],b[MAXN],ans,f[MAXN][MAXN];
bool cmp1(int x,int y){
    return x>y;
}
bool cmp2(int x,int y){
    return x<y;
}
int getans(){
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            f[i][j]=INT_MAX;
            for(int k=1;k<=j;k++)
				f[i][j]=min(f[i][j],f[i-1][k]+abs(b[i]-a[j]));
        }
    }
    int ans=INT_MAX;
    for(int i=1;i<=n;i++)ans=min(ans,f[n][i]);
    return ans;
}
int main(){
    read(n);
    for(int i=1;i<=n;i++)read(a[i]),b[i]=a[i];
    sort(a+1,a+n+1,cmp1);
    ans=getans();
    sort(a+1,a+n+1,cmp2);
    ans=min(ans,getans());
    cout<<ans;
    return 0;
}

您会发现这个算法复杂度是 O(n3)O(n^3) 的,所以,还需要优化。我们发现找最小值,会有很多的重复计算

所以,我们可以把这个算法,优化成 O(n2)O(n^2)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
    T RR=1;FF=0;char CH=getchar();
    for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
    for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
    FF*=RR;
}
template<typename T>inline void write(T x){
    if(x<0)putchar('-'),x*=-1;
    if(x>9)write(x/10);
    putchar(x%10+48);
}
template<typename T>inline void writen(T x){
    write(x);
    puts("");
}
const int MAXN=2e3+10;
int n,a[MAXN],b[MAXN],ans,f[MAXN][MAXN];
bool cmp1(int x,int y){
    return x>y;
}
bool cmp2(int x,int y){
    return x<y;
}
int getans(){
    for(int i=1;i<=n;i++){
        int ans=INT_MAX;
        for(int j=1;j<=n;j++){
            ans=min(ans,f[i-1][j]);
            f[i][j]=ans+abs(b[i]-a[j]);
        }
    }
    int ans=INT_MAX;
    for(int i=1;i<=n;i++)ans=min(ans,f[n][i]);
    return ans;
}
int main(){
    read(n);
    for(int i=1;i<=n;i++)read(a[i]),b[i]=a[i];
    sort(a+1,a+n+1,cmp1);
    ans=getans();
    sort(a+1,a+n+1,cmp2);
    ans=min(ans,getans());
    cout<<ans;
    return 0;
}
原文地址:https://www.cnblogs.com/zhaohaikun/p/12816935.html