上次TYVJ有一道裸LIS,然而我當時直接打了一個N^2暴力就草草了事,然后就ZZ了,只拿了60分,其實NlogN的LIS和N^2的差的不多,只是沒有N^2,好想罷了,鑒於某學弟的要求,所以就重現一下金哥當年講LIS的風范。
首先,LIS指的是最長上升子序列。指的是我們要求出一個在母序列中找出一些元素,在保證這個子序列上升的同時,保證這個序列是整個母序列里滿足這一要求的最長序列。
那么我們可以直接這樣想,我們要保證當前所拼接的鏈最大,那么對於每一個元素來說,可以直接一遍循環判斷它能夠屬於哪一個鏈,使當前以這個元素為結尾的鏈所接的元素個數最多,也就是所得的結果越大。
那么對於一個元素ai,可以直接找a1到ai-1的元素中ai鏈在哪一個元素所得解最優,最優解放在f[i]里,這種對於所有情況的判斷,很明顯是DP,那么,轉移方程就是:f[i]=a[j]<a[i]?max(f[j]+1,f[i]):f[i]
那么明顯的,這是一個N^2的算法,對於每一個元素要求以前是否有能夠找到更優解的上一步元素,一個判斷N遍。
那如何得到一個更優算法捏?
其實很簡單,讓我們先回顧剛才我們所做的操作,我們對於當前的一個元素,找前面所算過的所有結果,試圖找出更優解。我們在做這一工作時,也找了很多肯定不為最優解的元素,這是一件很浪費的事情,那么我們可以找一個優化這一過程的方法。
我們知道,對於任意一個元素,使它的解更優的方案肯定是a[i]小,並且f[i]大的一個理想元素,因為對於這樣的一個理想元素,才可能使后面的解更優,而最大的f[i]是有限的,所以,我們很輕易得想到一個優化方法:
使用一個數組d,用d[i]記錄當前 f[i] 為 i 的 a[i]最小的元素,d隨着元素的向后遞推逐漸維護。
開一個數組就能使LIS更優嗎?答案是當然的,原因很簡單,我們發現,我們所維護的這個d數組是遞增的,這一結論可以通過反證法易證。既然這一數組是遞增的,我們就可以輕易的通過二分來得到最優解。
所以,對於每一個元素做一遍二分,顯而易見復雜度是nlogn的。
附上一道水題 codevs
LIS問題是最經典的動態規划基礎問題之一。如果要求一個滿足一定條件的最長上升子序列,你還能解決嗎?
給出一個長度為N整數序列,請求出它的包含第K個元素的最長上升子序列。
例如:對於長度為6的序列<2,7,3,4,8,5>,它的最長上升子序列為<2,3,4,5>,但如果限制一定要包含第2個元素,那么滿足此要求的最長上升子序列就只能是<2,7,8>了。
第一行為兩個整數N,K,如上所述。
接下來是N個整數,描述一個序列。
請輸出兩個整數,即包含第K個元素的最長上升子序列長度。
8 6
65 158 170 299 300 155 207 389
4
80%的數據,滿足0<n<=1000,0<k<=n
100%的數據,滿足0<n<=200000,0<k<=n
1 #include<stdio.h> 2 int cnt,n,a[200100],best[200100]; 3 void push(int x) 4 { 5 int L=0,R=cnt; 6 int mid; 7 while(L<=R) 8 { 9 mid=(L+R)/2; 10 if(x>best[mid]){ 11 L=mid+1; 12 if(best[L]>x)best[L]=x; 13 } 14 else R=mid-1; 15 } 16 } 17 int main() 18 { 19 int k,i; 20 scanf("%d%d",&n,&k);k--; 21 for(i=0;i<=n-1;i++) 22 { 23 scanf("%d",&a[i]); 24 if(i>k&&a[i]<=a[k]){i--;n--; 25 } 26 } 27 for(i=0;i<=k-1;i++) 28 if(a[i]>=a[k])a[i]=0; 29 for(i=0;i<=n-1;i++) 30 { 31 if(a[i]<best[cnt]&&a[i])push(a[i]); 32 else if(a[i])best[++cnt]=a[i]; 33 } 34 printf("%d",cnt); 35 return 0; 36 }