路面修整的題解

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&dbd\geq a \And d\leq b

這種情況,顯然,對不對。比如 a,b,ca,b,c 的最優排列是 3,5,7,93,5,7,9d=4d=4。我覺得不用解釋了。

  • db&dcd\geq b \And d\leq c

這種情況,顯然,對不對。比如 a,b,ca,b,c 的最優排列是 3,5,7,93,5,7,9d=6d=6。我覺得不用解釋了。

  • dcd\geq 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章