先給出模板題目鏈接
遇到最長不下降子序列, 我們第一時間想到的是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;
}