1. 程式人生 > >九度OJ 1533 最長上升子序列 (基於貪心和二分查詢)

九度OJ 1533 最長上升子序列 (基於貪心和二分查詢)

題目描述:

給定一個整型陣列, 求這個陣列的最長嚴格遞增子序列的長度。 譬如序列1 2 2 4 3 的最長嚴格遞增子序列為1,2,4或1,2,3.他們的長度為3。

輸入:

輸入可能包含多個測試案例。
對於每個測試案例,輸入的第一行為一個整數n(1<=n<=100000):代表將要輸入的序列長度
輸入的第二行包括n個整數,代表這個陣列中的數字。整數均在int範圍內。

輸出:

對於每個測試案例,輸出其最長嚴格遞增子序列長度。

樣例輸入:
4
4 2 1 3
5
1 1 1 1 1
樣例輸出:
2
1

【思路分析】

   求LIS的經典問題,用到的不是DP裡面O(n*n)的方法,而是用了基於貪心、二分查詢的O(nlogn)方法(為什麼沒有命名成XXX演算法 = =)

   設a[n]為原序列,d[n]為長度為n的上升子序列的最後一個元素,當有多個長度為n的上升子序列時,取這些子序列中末尾最小的元素作為d[n]的值。為什麼選最小的呢?因為最小的最有“潛力”(貪心)。舉個例子,假設原序列為:2,1,8,3,7,5,6,對於d[3],長度為3的上升子序列有1,3,7和1,3,5兩個,那麼d[3]取值為5,即末尾最小的元素。因為在這個子序列後可能存在x滿足 5 < x < 7(即x == 6),因此要是子序列確定為1,3,7的話,則顯然不如1,3,5,6長。可見,d陣列中的元素是單調遞增的。

   有了上述的貪心策略,便可以進行下面的操作了。首先,令len = 1,d[1] = a[1],當a[i] > d[len]時,有d[++len] = a[i],即加入新的元素來擴充上升子序列;否則,從d[1]到d[len - 1]找到一個j,使得a[i]滿足:  d[j - 1] < a[i] < d[j],這時有d[j] = a[i],即用a[i]來替換d[j](也就是a[i]比d[j]更有“潛力”)。又由於d陣列是單調遞增的,因此可以用二分查詢O(logn)很快找到j的值。

   最後用a = {2,1,5,3,6,4,8,9}這個序列來過一遍上述的流程。首先,len = 1,d[1] = a[1] = 2。則a[i],len,d[len]的值的變化見下表:

  

程式碼如下:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 100005;
int n;
int a[maxn];
int d[maxn];//記錄長度為i的上升子序列最後一個元素的值
int binSearch(int key,int left,int right)
{
    while(left <= right)
    {
        int mid = (left + right) >> 1;
        if(key > d[mid] && key <= d[mid + 1])//貪心策略
        {
            return mid;
        }
        else if(key > d[mid])
        {
            left = mid + 1;
        }
        else
        {
            right = mid - 1;
        }
    }
    return 0;
}
int LIS(int n)
{
    d[1] = a[1];
    int len = 1;
    int j = 0;
    for(int i = 2;i <= n;i++)
    {
        if(d[len] < a[i])
        {
            j = ++len;//直接向後插入
        }
        else
        {
            j = binSearch(a[i],1,len) + 1;//找到替換位置
        }
        d[j] = a[i];
    }
    return len;
}
void init()
{
    for(int i = 1;i <= n;i++)
    {
        scanf("%d",&a[i]);
    }
}
void solve()
{
    printf("%d\n",LIS(n));
}
int main()
{
    while(scanf("%d",&n) != EOF)
    {
        init();
        solve();
    }
    return 0;
}