1. 程式人生 > >[四連測(二)]奶牛慢跑

[四連測(二)]奶牛慢跑

題目描述

有n(n<=100000)頭奶牛在一個無窮長的小道上慢跑。每頭奶牛的起點不同,速度也不同。小道可以被分成多條跑到。奶牛隻能在屬於自己的跑道上慢跑,不允許更換跑道,也不允許改變速度。如果要慢跑t(t<=1000000000)分鐘,要保證在任何時候不會有同一跑道上的奶牛相遇,請問最少需要多少條跑道。奶牛開始在哪條跑道是可以隨意設定的。

輸入

輸入格式:第一行兩個整數n,t。

接下來的n行,每行包含兩個整數,表示奶牛的位置和速度。位置是非負整數,速度是正整數。所有的奶牛的起點都不相同,按起點遞增的順序給出。
輸出

輸出格式:

最少的跑道數。

樣例輸入

5 3
0 1
1 2
2 3
3 2
6 1

樣例輸出

3

解題思路

要想要求出正確的答案,其實就必須要想到題目中的性質,打出合適的演算法。我們不看每一頭牛當前的速度與位置,因為這樣計算將會十分複雜,我們首先就必須要求出它們在最後的時候到達了哪裡,這樣,逆序的中途即會相遇。

那麼如何求出最少需要的跑道個數呢?考試的時候呢,我是這樣想的,每一個跑道都是上升的子序列,那麼就一定滿足條件,這樣求出最小,其實再想一想,如果求出了最長不上升子序列,不就可以得出答案了嗎?最長不上升子序列中的元素任意兩兩一定會相遇的。因此,它們都需要單獨一個跑道,而其他的呢就無所謂了。

因此,這道題就是求一個最長不上升子序列,相信這種DP題目大家都會,但樸素的DP演算法時間複雜度是N方的,此題是過不了的。那麼關鍵就是要看如何進行優化了。如果大家看過一些書籍,應該對於運用lower_bound函式進行二分優化的最長不下降子序列有一定印象。確實,這道題可以說就是運用二分進行優化。

二分要滿足單調性,因此我們需要排序。

之後,將陣列中的數一一進行二分,找到合適位置而放進去,保持陣列呈不上升。

這裡估計有人會有疑問了,為什麼這樣就真能保證數組合理呢?首先,最長肯定是對的,我們可以很輕鬆判斷出來,但是否真的存在這樣的序列呢?

我們給出簡單的一些資料進行討論 如(3 , 2  , 8) ,處理完後,就變成了( 8 , 2)。很顯然,這一種數列是不存在的。

但我們再看一下,它的長度並沒有變化。最長不下降子序列的長度的確是陣列的長度2。

那麼再在後面加一些資料? (3 , 2 , 8 ,1) , 處理完後,變成了 ( 8 ,2 , 1)。這種數列更奇怪了,然而,答案卻還是這麼多。其實,將8放入隊頭,我們可以看出這可能是一個更有用的數字,因為它大,所以後面空間可能更大。而放入隊頭,雖然看起來不合理,但並不會改變答案的最優性質。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<vector>
#include<stack>
using namespace std;
   
#define LL long long
#define N 100005
   
struct node {
    long long  x;
    int id;
}a[N];
int n,len = 1; 
long long maxt , dp [N],t;
int read(){
    int f = 1 , 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 f * x;
}
int solve(long long x){//二分求解
    int l = 1 ,r = len + 1;
    while (l + 1 < r){
        int mid = (l + r)/ 2;
        if (dp[mid] >= x){
            l = mid;
        }
        else
            r = mid;
    }
    if (dp[l] < x)
        return l;
    else
        return r;
}
int main(){
   // freopen("shuju.in","r",stdin);
    n = read();
    t = read();
    for (int i = 1;i <= n ;i ++ ){
        a[i].id = i;
        long long  t1 = read();
        long long  q = read();
        a[i].x = 1ll * t1 + 1ll *  q * t ;//求出終點
        maxt = max (a[i].x , maxt);//確定r範圍
    }
    dp[1] = a[1].x;
    for (int i = 2 ;i <= n; i ++ ){//將陣列每一個都進行處理
        if (dp[len] >= a[i].x) {
            dp[++ len] = a[i].x;
            continue;
        }
        else{   
            int j = solve(a[i].x);
            dp[j] = a[i].x;
            len = max(len,j);
        }
    }
    printf("%d",len);
}

總結

考試的時候,確實沒有看出這道題的性質,當時卻也是想到了最長不上升子序列,但總覺得這樣的做法不是最優,至於為什麼,我也不知道。。。考試的時候,我覺得這道題就像是遞迴求出每一個最長不下降子序列,其實如果我再細細想想的話,這道題不就是求一個最長不上升子序列嗎?主要還是被第一題困擾了,打亂了後面所有題的進度。