1. 程式人生 > >最長上升子序列(LIS)-O(nlogn)演算法總結.

最長上升子序列(LIS)-O(nlogn)演算法總結.

最近做了poj-1836這道題,用O(n^2)的LIS演算法TLE了,百度了很久大概的學會了LIS的O(nlogn)演算法;

最長上升子序列,簡稱LIS,O(n^2)的演算法這裡我就不寫了,百度一下很容易理解;

1.首先定義一個數組num[n]:即為題目給出的序列;

2.其次定義一個數組LIS[len]:LIS[len]的值表示長度為len的子序列的最小末元素;

3. LIS[len]中的元素是單調遞增的;

4.開始時len=1;LIS[1]=num[1];然後對num[i]:若num[i]>LIS[len],那麼len++,LIS[len] = num[i];

5.否則,我們要從LIS[1]到LIS[len-1]中找到一個j,滿足LIS[j-1]<num[i]<LIS[j],則根據LIS的定義,我們需要更新長度為j的上升子序列的最末元素(使之為最小的)

即 LIS[j] = num[i];

6.最終答案就是len,利用LIS的單調性,在查詢j的時候可以二分查詢,從而時間複雜度為nlogn。

=================================================

下面舉個例項:

假設存在一個序列num[9] = 2 6 8 3 5 4 10 9 3;

1.當i=1時,LIS[1]=num[1]=2,及長度為1的上升子序列的最小末元素為2,len=1;

2.當i=2時,此時先判斷num[i]與LIS[len]的關係(num[2]與LIS[1]的關係),由於num[2]>LIS[1],所以len++;LIS[len]=num[i](LIS[2]=num[2]=6)

;即長度為2的上升子序列的最小末元素為6,len=2;LIS[1...2]=2,6;

3.當i=3時,num[3]>LIS[2],所以len++;LIS[len]=num[i](LIS[3]=num[3]=8);即長度為3的上升子序列的最小的末元素為8 ,len=3;LIS[1...3]=2,6,8;

4.當i=4時,num[4]<LIS[3],所以在LIS[1..3]=2,6,8中找到比num[4]=3大的最小的數然後將它替換成num[4]這裡就是將LIS[2]=6替換成3,即LIS[2]=3,即長度為2的最小末元素為3,此時len=3,LIS[1...3]=2,3,8;

5.當i=5時,num[5]<LIS[3],在LIS[1..3]=2,3,8中找到比num[5]=5大的最小的數然後將它替換成num[5]這裡就是將LIS[3]=8替換成5,即LIS[3]=5,

即長度為3的最小末元素為3,此時len=3,LIS[1...3]=2,3,5;

6.當i=6時,num[6]<LIS[3],在LIS[1..3]=2,3,5中找到比num[6]=4大的最小的數然後將它替換成num[6]這裡就是將LIS[3]=5替換成4,即LIS[3]=4,即長度為3的最小末元素為4,此時len=3,LIS[1...3]=2,3,4;

7.當i=7時,num[7]>LIS[3],所以len++;LIS[len]=num[7]=10;即長度為4的子序列的最小末元素為10,len=4;LIS[1..4]=2,3,4,10;

8.當i=8時,num[8]<LIS[4],在LIS[1..4]=2,3,4,10中找到....即將LIS[4]替換成8,此時len=4;LIS[1..4]=2,3,4,8;

9.當i=9時,num[9]<LIS[4],在LIS[1..4]=2,3,4,8中找到比num[9]=3大的最小的末元素為然後將它替換成num[9]這裡就是將LIS[3]=4替換成3,即LIS=[3],即此時長度為3的子序列的最小末元素更新為3;此時len=4;LIS[1..4]=2,3,3,8;

此時說明這個數列num的最長上升子序列的長度為4

注意LIS表示的並不是那個最長的子序列,它只是儲存長度為i的子序列的最小末元素,最後一步看似沒有用處,實際上假如給num新增兩個元素3,5那麼可以將d[4]更新為3 d[5]更新為5這樣長度就變成了5;

在B中插入資料是有序的,而且是進行替換而不需要挪動——也就是說,我們可以使用二分查詢,將每一個數字的插入時間優化到O(logN)

程式碼如下:

#include<cstdio>
#include<cstring>
#define MAXN 40005

int arr[MAXN],ans[MAXN],len;

int binary_search(int i){
	int left,right,mid;
	left=0,right=len;
	while(left<right){
		mid = left+(right-left)/2;
		if(ans[mid]>=arr[i]) right=mid;
		else left=mid+1;
	}
	return left;
}

int main()
{
	int T,p,i,j,k;
	scanf("%d",&T);
	while(T--)
        {
		scanf("%d",&p);
		for(i=1; i<=p; ++i)
			scanf("%d",&arr[i]);

		ans[1] = arr[1];
		len=1;
		for(i=2; i<=p; ++i){
			if(arr[i]>ans[len])
				ans[++len]=arr[i];
			else{
				int pos=binary_search(i);
				ans[pos] = arr[i];
                               }
		}
		printf("%d\n",len);
	}
	return 0;
}