最長上升子序列 nlogn (導彈攔截)

先給出模板題目鏈接

攔截導彈

遇到最長不下降子序列, 我們第一時間想到的是O(n^2)的算法, 該算法簡便易懂而且代碼也好寫, 不像nlogn這個, 代碼幾分鐘,邊界問題調試了我幾十分鐘......

先來講理解思路

對於n(log n)這個解法, 除了一個保存數據的數組之外,還需要一個額外的數組(前者在一定情況下省略,也就是說邊輸入邊進行運算).

定義另一個數組爲 tmp[maxn](數組始終有序,具體爲什麼請往下面看)

除此之外需要一個p=1用來表示最長上升序列的個數

下面用圖形表示代碼運行過程

序列:   1 3 2 4 5

初始我們的tmp數組是這樣的

在3到的時候發現3比P[1]大,於是將3放到1的後面,此時上升序列有兩個.

數組變成了這個樣子

然後後面又來了一個2, 這個時候發現3比2要大, 這個時候我們用二分查找去找2可以更改的位置

發現2可以放在p[1]後面, 於是把p[2]更新爲2.( 爲什麼更新以及更新的作用我會在後面解釋, 請耐心往下看 ).

接下來的數組是這個樣子的

後面緊跟序列4 5, 是一個遞增的序列, 4比P[2]大,所以4放到P[2]後面, p[3] = 4, 然後 5比p[3]大, p[4] = 5, 數組是這個樣子的

爲了解釋之前爲什麼用2去更改3,我們在給定的序列1 3 2 4 5 後面加幾個數, 2 2 2,

這樣的話標準的不上升序列是1 2 2 2 2.然而到5爲止,我們求到的序列是1 2 4 5.

後面繼續用該算法去求

進來一個2,發現2比P[4]=5要小,於是找到他能插入的最大的位置,於是找到了p[3]=4, 將p[3]置爲2,該數列變成

然後又進來一個2, 發現p[4]還是要大於2, 所以繼續尋找可以插入的位置, 然後把p[5]更新成2.

於是數列變成

然後進來一個2,插入到最後序列就變成了

結果√

最後總結一句, 因爲是最長不下降子序列, 我們需要考慮的只有最後選擇的一個數,

前面已經選擇的數的更改是不會影響到最終的長度的,反而如果前面的數一直更改到了最後一個

說明這裏用一個更小的數將最後一個數給替代了, 因爲對於後面我們不知道的序列, 爲了達到最長, 我們當前選擇的這個數當然越小越好.

(...說不清楚了不說了...)

下面貼出導彈攔截nlogn代碼

解題方法: 先求一邊最長不上升子序列,再求一邊最長上升子序列

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 100005;
int sz[maxn],tp[maxn]; //
int n=1;
int p=1;
int main(){
	while(cin>>sz[n]) n++;
	tp[1] = sz[1];
	for(int i=2;i<n;i++){
		if(sz[i] <= tp[p]) tp[++p] = sz[i];//注意<= 最長不上升 
		else{
			int l=1,r=p,mid=p>>1;
			while(l!=r){
				if(sz[i] > tp[mid]) r = mid;
				else l = mid+1;
				mid = (l+r)>>1;
			}
			tp[l] = sz[i];
		}
	}
	cout<<p<<endl;
	memset(tp,0,sizeof(tp));
	tp[1]=sz[1];
	p=1;
	for(int i=2;i<=n;i++){
		if(sz[i] > tp[p]) tp[++p] = sz[i];
		else{
			int l=1,r=p,mid=p>>1;
			while(l!=r){
				if(sz[i] <= tp[mid] ) r = mid;
				else l = mid+1;
				mid = (l+r)>>1;
			}
			tp[l] = sz[i];
		}
	}
	cout<<p<<endl;
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章