這題挺簡單。
看圖就懂意思了,求不相交的線數目。
什麼時候兩條線交叉呢? --- i > j 但 a[i] < b[j] 的時候。
由於題目中a[i] = i ;
那就簡單地成爲了一個經典 ” 最長上升子序列 “問題啦。
數據到了w的級別,用 O(nlgn)的貪心方法做。
具體怎麼做呢?其實是用了額外的一個數組stk,其中stk[0]存下當前stk的長度,我們的目標是維護stk是遞增的
每次取待求數組的元素 signals[i], 將其與stk的最後一位相比較,如果比它大,則將它加入stk,當前stk的長度就是此時此刻的LIS
那麼如果比stk的最後一位小,那麼當然無法將其加入stk的末尾了。那麼此時把signal加入到stk中去替換掉“第一個出現比signal大的數”
依次這麼掃描待求數組並作處理,最後stk的長度就是LIS的長度了。
至於爲什麼這麼做,請允許我爲《算法引論》做個小解讀吧(純粹個人意見,可能錯得離譜)。
回顧我們想要的目標——最長上升子序列,下稱LIS ( Longest Increasing Sequence)
歸納假設:假設我們知道如何對前m個數求其長度爲k(k從1開始遞增到最長)的最有潛力的LIS,
最有潛力——什麼意思呢?也就是更方便我們後面讀到數之後加入其中。
那麼它必須是長度爲k的LIS裏(可能有多個),結尾數最小的。
那麼當處理第m+1個數的時候,我們有很多種處理情況——
1.首先如果它比最長的那個LIS(比如它的長度是x?)的尾巴還大,那麼可以直接加入末尾,得到一個新的長度爲x+1的一個LIS(原來那個長度x的LIS還是以原來的尾巴結尾,跟這個不矛盾)。
2.如果不是,那麼我們逐次去看長度爲(x-1, x-2, 。。。1)的LIS,看能否加入其中的某個,組成一個新的。
來了——如果可以加入,那麼組成一個新的,勢必會跟原來的某個LIS長度相等了,這時他們就要“競爭”了,PK掉一個,PK掉那個尾巴大的,留下尾巴小的,那麼可以給未來更大的可能。
於是在這種情況下,我們可以知道,每次我們總能找到一個LIS,然後去遞增它的長度,right?如果實在不行,那麼它自己成爲一個長度爲1的LIS,並理所當然地PK掉其他長度爲1的LIS(反正:如果不能PK掉它,那說明它比那個大,那麼可以接在後面組成長度2的LIS,對吧)。
當我們把所有元素掃描完,我們自然就得到了最長的LIS了,而且保證不會錯過任何一個可能(就是那些本來不是最長,後來慢慢增長並超越冠軍成爲最長的LIS)。
觀察到我們上面只提到這些LIS的“尾巴”,於是我們只需要存下“尾巴”們和它們代表的LIS的長度就夠了。
那麼用一個數組完全可以做到這一點,stk[i] 表示 長度爲 i 的LIS現在的尾巴。
現在我們明白stk中“替換”的操作,其實就是“PK掉尾巴大的數”的過程了。
-這裏可能有個小疑問——爲什麼長度 k+1 的LIS的尾巴,一定不會比 長度 k 的LIS小呢?(爲什麼stk會是遞增的)
因爲如果不是這樣,那麼 長度 k 的那個,完全可以拿這個k+1的前k個去當LIS呀。
現在,我們得到了一個完整的算法了。由於stk有序,每次可以直接找到它該去的位置,二分找。
代碼:
#include <iostream>
#include <stdio.h>
using namespace std ;
int main () {
int t,p ;
// s 是容器 , a是收到的信號(圖)
int stk[40005] , signals[40005] ;
int low , high , mid ;
scanf ( "%d" , &t ) ;
while ( t-- ) {
scanf ( "%d" , &p ) ;
for ( int i = 0 ; i < p ; ++i ) scanf ( "%d" , &signals[i] ) ;
stk[0] = 1 ;
stk[1] = signals[0] ;
for ( int i = 1 ; i < p ; ++i ) {
// 二分查找容器中第一個比a[i]大的數
low = 1 ;
high = stk[0] ;
while ( low <= high ) {
mid = (low-high)/2+high ;
if ( stk[mid] < signals[i] ) {
low = mid+1 ;
}
else {
high = mid-1 ;
}
}
if ( low == stk[0]+1 ) ++stk[0] ;
stk[low] = signals[i] ;
}
printf ( "%d\n" , stk[0] ) ;
}
//system("pause") ;
return 0 ;
}