1. 程式人生 > >C++ 最長遞增子序列問題

C++ 最長遞增子序列問題

最長遞增子序列問題:在一列數中尋找一些數,這些數滿足:任意兩個數a[i]和a[j],若i

動態規劃

求一序列的最長子序列的長度,那麼將問題變為在序列中求以a[k]為終點的最長上升子序列的長度aLen[k],狀態轉移方程為:
aLen[0] = 1
aLen[k] = MAX{aLen[i] ,0 <= i < k且a[i] < a[k]} + 1
因為在a[k]左邊結尾小於a[k]的序列的長度的最大值加上一就是以a[k]結尾的序列的最大長度。

const int MAX = 10000;
int a[MAX];//原始資料
int aLen[MAX];//存以第i個數為終點的序列長度
int Dynamic(int *a,int *aLen,int l) { int n,m; int maxLen = 1; for(n = 0;n < l;++n) aLen[n] = 1; for(n = 1;n< l;++n) { aLen[n] = 1;//最小為一 for(m = 0;m < n;++m)//尋找以a[n]結尾的最長序列(末尾比a[n]小且長度最長的序列) if(a[n] > a[m] && aLen[m] + 1 > aLen[n]) aLen[n] = aLen[m] + 1
; if(aLen[n] > maxLen)//標記當前的最長序列 maxLen = aLen[n]; } return maxLen; } int main() { int l; for(int i = 0;i < MAX;++i) { l = rand()%20; a[i] = l; cout << l << endl; } cout << "answer:" <<Dynamic(a,aLen,MAX); }

改進版

原文
考慮兩個數a[x]和a[y],x < y且a[x] < a[y],且dp[x]=dp[y],當a[t]要選擇時,到底取哪一個構成最優的呢?顯然選取a[x]更有潛力,因為可能存在a[x] < a[z] < a[y],這樣a[t]可以獲得更優的值。在這裡給我們一個啟示,當dp[t]一樣時,儘量選擇更小的a[x].
按dp[t]=k來分類,只需保留dp[t]=k的所有a[t]中的最小值,設d[k]記錄這個值,d[k]=min{a[t],dp[t]=k}。
這時注意到d的兩個特點(重要):
1. d[k]在計算過程中單調不升;
2. d陣列是有序的,d[1] < d[2] < ..d[n]。
利用這兩個性質,可以很方便的求解:
1. 設當前已求出的最長上升子序列的長度為len(初始時為1),每次讀入一個新元素x:
2. 若x>d[len],則直接加入到d的末尾,且len++;(利用性質2)
否則,在d中二分查詢,找到第一個比x小的數d[k],並d[k+1]=x,在這裡x<=d[k+1]一定成立(性質1,2)。

/** 
最長遞增子序列O(nlogn)演算法: 
狀態轉移方程:f[i] = max{f[i],f[j]+1},1<=j<i,a[j]<a[i]. 
分析:加入x<y,f[x]>=f[y],則x相對於y更有潛力。 
首先根據f[]值分類,記錄滿足f[t]=k的最小的值a[t],記d[k]=min{a[t]},f[t]=k. 
    1.發現d[k]在計算過程中單調不上升 
    2.d[1]<d[2]<...<d[k] (反證) 1 2 3 8 4 7 
解法: 
1. 設當前最長遞增子序列為len,考慮元素a[i]; 
2. 若d[len]<a[i],則len++,並將d[len]=a[i]; 
   否則,在d[0-len]中二分查詢,找到第一個比它小的元素d[k],並d[k+1]=a[i].() 
*/  
#include <iostream>  
#include <cstdio>  
#include <cstring>  
using namespace std;  
const int N = 41000;  
int a[N];       //a[i] 原始資料  
int d[N];       //d[i] 長度為i的遞增子序列的最小值  

int BinSearch(int key, int* d, int low, int high)  
{  
    while(low<=high)  
    {  
        int mid = (low+high)>>1;  
        if(key>d[mid] && key<=d[mid+1])  
            return mid;  
        else if(key>d[mid])  
            low = mid+1;  
        else  
            high = mid-1;  
    }  
    return 0;  
}  

int LIS(int* a, int n, int* d)  
{  
    int i,j;  
    d[1] = a[1];  
    int len = 1;        //遞增子序列長度  
    for(i = 2; i <= n; i++)  
    {  
        if(d[len]<a[i])  
            j = ++len;  
        else  
            j = BinSearch(a[i],d,1,len) + 1;  
        d[j] = a[i];  
    }  
    return len;  
}  

int main()  
{  
    int t;  
    int p;  
    scanf("%d",&t);  
    while(t--)  
    {  
        scanf("%d",&p);  
        for(int i = 1; i <= p; i++)  
            scanf("%d",&a[i]);  
        printf("%d\n",LIS(a,p,d));  
    }  
    return 0;  
}