LCS的優化演算法:O(nlogn)級別
阿新 • • 發佈:2018-11-10
一、參考連結:
1、演算法部分:http://www.cnblogs.com/itlqs/p/5743114.html
2、二分搜尋upper_bound and lower_bound:https://blog.csdn.net/qq_40160605/article/details/80150252
二、例題:
洛谷:P1020導彈攔截:https://www.luogu.org/problemnew/solution/P1020
題目大意:求最長不上升子序列與不上升子序列個數
思路分析:
1、最長不上升子序列的樸素dp為O(n2)級別,對於10W這樣的大資料還是處理不了,於是需要更為優秀的O(nlogn)級別的演算法。
具體思路如下:使用陣列d來為維護子序列,d[i]表示:長度為i的子序列最後一位的最大值
考慮新進來一個元素a[i]:
如果這個元素大於等於d[len],直接讓d[len+1]=a[i],然後len++。這個很好理解,當前最長的長度變成了len+1,而且d陣列也添加了一個元素。
如果這個元素小於d[len]呢?說明它不能接在最後一個後面了。那我們就看一下它該接在誰後面。
準確的說,並不是接在誰後面。而是替換掉誰。因為它接在前面的誰後面都是沒有意義的,再接也超不過最長的len,所以是替換掉別人。那麼替換掉誰呢?就是替換掉那個最該被它替換的那個。也就是在d陣列中第一個
至於第一個大於它的怎麼找……STL upper_bound。每次複雜度logn。
2、不上升子序列個數,根據Dilworth定理得知:
(1)U的鏈劃分使用的最少集合數,等於它的最大反鏈長度。
(2)U的反鏈劃分使用的最少集合數,等於它的最大鏈長度。
(大概可以理解為,一個集合,其中的序列最小劃分數等於反向的最長序列長度)
根據這個定理,就可以得知,不上升子序列的數量就是上升子序列的最大長度(而不下降的相反就是下降)
所以答案就是上升子序列的最大長度,解決方法如(1)
AC程式碼:
#include<bits/stdc++.h>
#define MAX_N 100005
using namespace std;
//struct cmp{bool operator()(int a,int b){return a>b;}};
int main(){
int n,lendown,lenup;
int sum=0;
int down[MAX_N];
int up[MAX_N];
int a[MAX_N];
while(scanf("%d",&n)!=EOF){
a[++sum]=n;
}
if(sum==0){
cout<<"0"<<endl;
cout<<"0"<<endl;
return 0;
}
down[1]=a[1];
up[1]=a[1];
lendown=1;
lenup=1;
// for(int i=1;i<=sum;i++)cout<<a[i]<<" ";
for(int i=2;i<=sum;i++){
if(a[i]<=down[lendown])
down[++lendown]=a[i];
else{
int j=upper_bound(down+1,down+lendown+1,a[i],greater<int>())-down;
// cout<<j<<endl;
down[j]=a[i];
}
if(a[i]>up[lenup])
up[++lenup]=a[i];
else{
int j=lower_bound(up+1,up+lenup+1,a[i])-up;
up[j]=a[i];
}
}
cout<<lendown<<endl<<lenup<<endl;
// for(int i=1;i<=lendown;i++){
// cout<<down[i]<<" ";
// }cout<<endl;
// for(int i=1;i<=lenup;i++){
// cout<<up[i]<<" ";
// }cout<<endl;
return 0;
}