1. 程式人生 > >第三章 迴圈佇列及線性結構綜合-計算機17級 7-2 列車排程 (25 分)

第三章 迴圈佇列及線性結構綜合-計算機17級 7-2 列車排程 (25 分)

7-2 列車排程 (25 分)

火車站的列車排程鐵軌的結構如下圖所示。

兩端分別是一條入口(Entrance)軌道和一條出口(Exit)軌道,它們之間有N條平行的軌道。每趟列車從入口可以選擇任意一條軌道進入,最後從出口離開。在圖中有9趟列車,在入口處按照{8,4,2,5,3,9,1,6,7}的順序排隊等待進入。如果要求它們必須按序號遞減的順序從出口離開,則至少需要多少條平行鐵軌用於排程?

輸入格式:

輸入第一行給出一個整數N (2 ≤ N ≤10​5​​),下一行給出從1到N的整數序號的一個重排列。數字間以空格分隔。

輸出格式:

在一行中輸出可以將輸入的列車按序號遞減的順序調離所需要的最少的鐵軌條數。

輸入樣例:

9
8 4 2 5 3 9 1 6 7

輸出樣例:

4

思路:

這個題還是挺有價值的。你仔細看看,會發現其實他在考你最長上升子序列問題(LIS)。關於什麼是LIS,一會再說,在這裡先介紹一下為啥這個題是在考LIS,這裡就要用到一個定理,叫做Dilworth定理,這是組合數學中的一個定理。關於這個定理,我建議你就別百度搜是啥了,除非是大牛,一般人真看不懂。。。

我說的這個應該就夠了,定理的大致意思是:

在一個序列中 最長下降子序列的個數就等於其最長不下降子序列的長度

舉例:1 2 3 2 3

  最長下降子序列:3 2-->長度為2

  最長上升子序列:1 2 3-->長度為3

反之也一樣。

哈哈好懂吧。

因為此題中沒有重複值,所以其實就是求最長上升子序列的長度。

關於求解LIS問題的方法有兩種,一種時間複雜度為O(n^2)還有一種為O(n log n),這個題得用第二種,用第一種肯定會超時。

相信看完你就懂了

具體答案如下:

#include<cstdio>
#include<algorithm>
#include <bits/stdc++.h>
const int MAXN=200001;

int a[MAXN];
int d[MAXN];
int find(int a[],int start,int end,int key)//二分查詢,返回a陣列中第一個>=key的位置
{
    int left = start;
    int right = end;
    int mid;
    while(left <= right)
    {
        mid=(left + right)/2;
        if(d[mid] > key)
            right = mid - 1;
        else
            left = mid + 1;
    }
    return left;
}

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    d[1]=a[1];//代表下標都從0開始
    int len=1;
    for(int i=2;i<=n;i++)//用陣列模擬棧
    {
        if(a[i]>=d[len])//每遇到一個比棧頂元素大的數,就放進棧裡,
            d[++len]=a[i];
        else//遇到比棧頂元素小的就二分查詢前邊的元素
        {
            //int j=std::lower_bound(d+1,d+len+1,a[i])-d;//找到下標
            int j = find(a,1,len,a[i]);//返回第一個大於等於key的元素
            d[j]=a[i];//並用它替換掉棧頂元素
            //if(len < j)
                //len = j;
        }
    }
    printf("%d\n",len);
    return 0;
}

當然還可以使用一些STL的容器或者函式

補充兩種用stl的方法:

原理:

Set不但自動排序,裡面還有upper_bound(num)函式,查詢與num最接近的比num大的數,省去了內層迴圈,時間複雜度縮小到了O(n)。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int num,n;
    cin>>n;
    set<int> s;
    for(int i=0;i<n;i++)
    {
        cin>>num;
        if(s.upper_bound(num)!=s.end())//如果找到了最接近num且比num大的數(==s.end代表沒找到)
            s.erase(s.upper_bound(num));//就把那個數刪了(num照常插入)(更新過程)
        s.insert(num);
    }
    cout<<s.size();
    return 0;
}

或者使用lower_bound( begin,end,num)函式

在從小到大的排序陣列中,

lower_bound( begin,end,num):從陣列的begin位置到end-1位置二分查詢第一個大於或等於num的數字,找到返回該數字的地址,不存在則返回end。通過返回的地址減去起始地址begin,得到找到數字在陣列中的下標。

upper_bound( begin,end,num):從陣列的begin位置到end-1位置二分查詢第一個大於num的數字,找到返回該數字的地址,不存在則返回end。通過返回的地址減去起始地址begin,得到找到數字在陣列中的下標。

關於它的使用方法可參考連結:https://blog.csdn.net/qq_40160605/article/details/80150252 

#include<cstdio>
#include<algorithm>
const int MAXN=200001;

int a[MAXN];
int d[MAXN];

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    d[1]=a[1];
    int len=1;
    for(int i=2;i<=n;i++)
    {
        if(a[i]>d[len])
            d[++len]=a[i];
        else
        {
            int j=std::lower_bound(d+1,d+len+1,a[i])-d;
            d[j]=a[i]; 
        }
    }
    printf("%d\n",len);    
    return 0;
}