uva12170 輕松爬山

給一組數(高度)h1,h2,...,hn,要求修改h2~h(n-1)的值,使得h1,h2,...,hn中任意相鄰兩個數的差的絕對值不超過d。將某個hx從a修改到b的代價為|a-b|,求最小總代價。
n<=100 d<=1e9

基本思路是常規的動態規劃,用dp[i][j]表示考慮前i個高度,并且第i個高度修改為j時的最小代價。但是這樣j的范圍太大,所以標準解答將dp的第二個維度離散化了。

可以證明這樣的結論:在最優解中,任何一個高度都可以寫成h_x+kd的形式,hx是h1~hn中的某個高度,k是(-2n,2n)之間的整數。

下面證明它。


假設上圖是最優解中某個任意位置x以及它左右的一些高度。先說明一下這個圖,每個方塊的中心表示它的高度,方塊邊長為d,相鄰方塊均相連從而相鄰高度差不超過d。

我們以x為中心向兩邊擴展所有只以1個點相連的其他高度,當出現相鄰兩個方塊的公共部分是線段時就停下,也就是我們只考慮圖中那些實線方塊。

如果這一組方塊中有任意一個處在自己原先的位置,那么x的位置顯然符合結論。如果它們全部都不在自己原先的位置,那么我們可以把這個整體向上或向下移動微小距離而保證整體代價不增加,這個過程持續到其中任意一個方塊移回原始位置或者虛線方塊被納入實線方塊內。至此,結論的正確性顯而易見。

如此狀態數是O(n3),但轉移似乎也要n2,時間復雜度承受不起。可以在計算dp(i,Hj)時按照Hj(Hj是一系列離散值)從小到大的順序來算,由于dp(i,Hj)=|h[i]-Hj|+min{dp(i-1,Hx)|Hx∈[Hj-d,Hj+d]},所以相當于計算Hx在區間[Hj-d,Hj+d]上dp(i-1,Hx)的最小值,可以用滑動窗口+單調隊列來處理,從而使轉移復雜度變成平攤后O(1)。

AC代碼:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define y second
#define x first
#define LL long long
#define INF 1e15
using namespace std;

const int maxn=100+5;
LL n,d,h[maxn],dp[maxn][2*maxn*maxn],f[2*maxn*maxn];
int fcnt;
pair<LL,int> q[2*maxn*maxn];

void solve(){
    fcnt=0;
    
    for(int i=0;i<n;i++)
        for(int k=-n+1;k<=n-1;k++)
            f[fcnt++]=h[i]+k*d;
    
    sort(f,f+fcnt);
    
    for(int j=0;j<fcnt;j++){
        dp[0][j]=INF;
    }
    
    int j0,je;
    for(int i=0;i<fcnt;i++){
        if(f[i]==h[0])j0=i;
        if(f[i]==h[n-1])je=i;
    }
    
    dp[0][j0]=0;
    
    for(int i=1;i<n;i++){
        int L=0,R=0;//[L,R)
        int ft=0,rr=0;//[ft,rr)
        for(int j=0;j<fcnt;j++){
            
            while(f[L]<f[j]-d){
                if(L==q[ft].y)ft++;
                L++;
            }
            while(R<fcnt&&f[R]<=f[j]+d){
                LL cur=dp[i-1][R];
                while(rr>ft&&cur<=q[rr-1].x)rr--;
                q[rr++]=make_pair(cur,R);
                R++;
            }
            
            if(q[ft].x!=INF){
                dp[i][j]=abs(f[j]-h[i])+q[ft].x;
            }else dp[i][j]=INF;
        }
    }
    
    if(dp[n-1][je]==INF)printf("impossible\n");
    else printf("%lld\n",dp[n-1][je]);
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%lld%lld",&n,&d);
        for(int i=0;i<n;i++)scanf("%lld",&h[i]);
        solve();
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 專業考題類型管理運行工作負責人一般作業考題內容選項A選項B選項C選項D選項E選項F正確答案 變電單選GYSZ本規程...
    小白兔去釣魚閱讀 9,051評論 0 13
  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,768評論 0 33
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽默認的外補...
    _Yfling閱讀 13,790評論 1 92
  • A股在千呼萬喚始出來中解開了她猶抱琵琶半遮面的的猙獰面貌,之前各路游資,新聞唱多的熱潮也冷了下來,不能說不對,...
    李曉瑋閱讀 443評論 0 0
  • 當哥倫布的后繼者們撥開雨林繁茂的枝葉,趟過河流,來到印第安人駐地的時候,他們第一次接觸了這種神奇的植物——煙草和古...
    曾誠閱讀 440評論 0 4