1. 程式人生 > >最長上升子序列 O(nlogn)解法 (轉)

最長上升子序列 O(nlogn)解法 (轉)

最近在做單調佇列,發現了最長上升子序列O(nlogn)的求法也有利用單調佇列的思想。

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

   設dp[i]表示以i為結尾的最長遞增子序列的長度,則狀態轉移方程為:

dp[i] = max{dp[j]+1}, 1<=j<i,a[j]<a[i].

   這樣簡單的複雜度為O(n^2),其實還有更好的方法。

   考慮兩個數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)。

  1. /** 
  2. 最長遞增子序列O(nlogn)演算法: 
  3. 狀態轉移方程:f[i] = max{f[i],f[j]+1},1<=j<i,a[j]<a[i]. 
  4. 分析:加入x<y,f[x]>=f[y],則x相對於y更有潛力。 
  5. 首先根據f[]值分類,記錄滿足f[t]=k的最小的值a[t],記d[k]=min{a[t]},f[t]=k. 
  6.     1.發現d[k]在計算過程中單調不上升 
  7.     2.d[1]<d[2]<...<d[k] (反證) 1 2 3 8 4 7 
  8. 解法: 
  9. 1. 設當前最長遞增子序列為len,考慮元素a[i]; 
  10. 2. 若d[len]<a[i],則len++,並將d[len]=a[i];
     
  11.    否則,在d[0-len]中二分查詢,找到第一個比它小的元素d[k],並d[k+1]=a[i].() 
  12. */  
  13. #include <iostream>  
  14. #include <cstdio>  
  15. #include <cstring>  
  16. using namespace std;  
  17. const int N = 41000;  
  18. int a[N];       //a[i] 原始資料  
  19. int d[N];       //d[i] 長度為i的遞增子序列的最小值  
  20. int BinSearch(int key, int* d, int low, int high)  
  21. {  
  22.     while(low<=high)  
  23.     {  
  24.         int mid = (low+high)>>1;  
  25.         if(key>d[mid] && key<=d[mid+1])  
  26.             return mid;  
  27.         else if(key>d[mid])  
  28.             low = mid+1;  
  29.         else  
  30.             high = mid-1;  
  31.     }  
  32.     return 0;  
  33. }  
  34. int LIS(int* a, int n, int* d)  
  35. {  
  36.     int i,j;  
  37.     d[1] = a[1];  
  38.     int len = 1;        //遞增子序列長度  
  39.     for(i = 2; i <= n; i++)  
  40.     {  
  41.         if(d[len]<a[i])  
  42.             j = ++len;  
  43.         else  
  44.             j = BinSearch(a[i],d,1,len) + 1;  
  45.         d[j] = a[i];  
  46.     }  
  47.     return len;  
  48. }  
  49. int main()  
  50. {  
  51.     int t;  
  52.     int p;  
  53.     scanf("%d",&t);  
  54.     while(t--)  
  55.     {  
  56.         scanf("%d",&p);  
  57.         for(int i = 1; i <= p; i++)  
  58.             scanf("%d",&a[i]);  
  59.         printf("%d\n",LIS(a,p,d));  
  60.     }  
  61.     return 0;  
  62. }