1. 程式人生 > >[四連測(一)]猴子

[四連測(一)]猴子

題目描述

有Q只猴子要從第一棵樹到第n棵樹去,第i只猴子一次跳躍的最遠距離為Ki。如果它在第x棵樹,那它最遠可以跳到第x+Ki棵樹。如果第j棵樹的高度比第i棵樹高或相等,那麼它從第i棵樹直接跳到第j棵樹,它的勞累值會增加1。所有猴子一開始在第一棵樹,請問每隻猴子要跳到第n棵樹花費的勞累值最小。

輸入

第一行一個整數n,表示有n棵樹。(2<=n<=1000000)

接下來第二行給出n個正整數D1,D2,……,Dn(1<=Di<=10^9),其中Di表示第i棵樹的高度。

第三行給出了一個整數Q(1<=Q<=25),接下來Q行,給出了每隻猴子一次跳躍的最遠距離Ki(1<=Ki<=N-1)。

輸出

輸出Q行,每行一個整數,表示一隻猴子的最小的勞累值。

樣例輸入

9
4 6 3 6 3 7 2 6 5
2
2
5

樣例輸出

2
1

解題思路

可以很明顯地看出來是DP題目。DP的思路還是比較好想,因為這一道題就是線性的,並不是說什麼多維那種,所以這道題DP並不複雜。

dp[i] = min(dp[j] + (h[i] >= h[j]))

這個狀態轉移方程後面的比較就是判斷從j到i是否+1,雖然看起來是一維的,但實現是需要O(n^2)的時間複雜度的,那麼這道題怎麼可能過得了。於是,我們便要考慮優化了。

我們看狀態轉移方程,其中的j是有一個範圍的,就是 i - k + 1 <= j <= i - 1。又是求最小值,我們便可以想到些什麼東西

的確,這道題需用單調佇列來進行優化。維護佇列中元素遞增,並且判斷隊首元素是否在區間內。這樣就得到了最小的dp[i]了。但怎麼確定優先順序呢。對於不同的高度還有加1和不加1的這一種情況呢。難道要將全部放進單調佇列中嗎?

答案是否定的。我們姑且拋掉後面的比較。先討論一下最小的dp[j]

(一)如果dp[j]最小且唯一,就假設它加上1吧。還是最小的,肯定可以選啊。這種情況很好討論。

(二)如果有多個最小值,我們便需判斷一下了,我們想想,說不定這些j所在的高度是有些需要加1,但有些是不需要加1的,那麼,不需要加1的那一些高度肯定優於其他的那些高度。但我們又不確定是否不用加1,但我們不考慮這麼多,在這些最小值找到一個最高的。最後判斷就行了,如果它不用+1,那是最好的,如果它要+1,其他的也一樣的。於是我們便討論完了所有情況,可以用單調佇列來做了

程式碼如下:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<cstdlib>
#define M 1000005
using namespace std;
struct node {
    int x,id;
    inline node (){};
    inline node (int X,int ID){
        x = X;
        id = ID;
    }
};
int n,a[M],dp[M],q;
int head = 1,tail;
node Queue[M];
inline int read(){
    int f = 1;
    int x = 0;
    char s = getchar();
    while (s < '0' || s > '9'){
        if (s == '-')
            f = -1;
        s = getchar();  
    }
    while (s >= '0' && s <= '9'){
        x = x * 10 + s - '0';
        s = getchar();
    }
    return x * f;
}
int main(){
    n = read();
    for (int i = 1;i <= n; i ++ ){
        a[i] = read();
    }
    q = read();
    for (int i = 1 ;i <= q ;i ++ ){
        head = 1, tail = 1;
        Queue[tail] = node (dp[1],1);
        int x = read();
        for (int j = 2;j <= n; j ++ ){
            while (Queue[head].id < j - x ){
                head ++ ;
            }
            if (a[Queue[head].id] > a[j]){
                dp[j] = dp[Queue[head].id];
            }
            else
                dp[j] = dp[Queue[head].id ] + 1;
            while (head <= tail ){
                node  L = Queue[tail];
                if (L.x < dp[j]){
                    Queue[++ tail] = node (dp[j], j);
                    break;
                }
                else if (L.x == dp[j] && a[Queue[tail].id] > a[j]){//這一個地方太重要了,考試時候沒寫卡了我60分。
                    Queue[++ tail] = node (dp[j], j);
                    break;
                }
                else
                    tail -- ;
            }
            if (head > tail){
                Queue[++ tail] = node(dp[j],j);
            }
        }
        printf("%d\n",dp[n]);
    }
}

總結

可以說就是考試的時候沒考考慮完全,導致這道題一半的分都沒有,其實思路大部分是對的。其實關於有多個最小值選哪個我是在腦中閃過這一個念頭的,當時的我自信地認為這一種情況應該是不用討論的。現在覺得當時就應該多想想。整道題的難度呢,我是覺得不大的。甚至可以說是模板,有些細節需要注意罷。

值得一提的是:此題似乎可以用貪心來做。就不用這一個單調佇列來優化DP了,不知道是資料的原因還是題目的特性,但DP可以說是沒有問題的,說不定所謂的貪心演算法也是運用了DP的思想,只不過寫出來沒這麼正式罷了。