咱們設狀態\(f_i\)爲以i結束的最長上升子序列的最長長度。則有則咱們從i前面找到一個元素,知足\(a_j<a_i\),枚舉全部知足條件的j,則i的f值就是這全部的j對應的f值加1。代碼:ios
for(int i=1;i<=n;++i) { for(int j=1;j<i;++j) if(a[j]<a[i]) f[i]=max(f[i],f[j]+1); }
咱們發現咱們浪費了一些時間在找知足\(a_j<a_i\)的f的最大值上,可否優化這個過程呢?固然能夠,咱們只須要一顆線段樹。這顆線段樹知足單點修改,區間查詢最大值,因此不用打懶標記。
這裏須要注意的是,線段樹裏的每一個位置是\(a_i\)這個值,而不是下標i,每次找到以i結尾的最大值時單點查詢\(a_i\),把這個位置的值改成\(f_i\),到了\(a_j\)查找最大值時查找的區間就是0至\(a_{j-1}\)這樣就保證了咱們解得合法性。c++
代碼:算法
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<sstream> #include<queue> #include<map> #include<vector> #include<set> #include<deque> #include<cstdlib> #include<ctime> #define dd double #define ll long long #define ull unsigned long long #define N 7001000 #define M 100100 using namespace std; inline int Max(int a,int b){ return a>b?a:b; } struct Xtree{// point_change square_ask->no_lazy int p[N]; inline Xtree(){ memset(p,0,sizeof(p)); } inline void pushup(int k){ p[k]=Max(p[k*2],p[k*2+1]); } inline void change(int k,int l,int r,int w,int x){ if(l==r&&l==w){ p[k]=x; return; } int mid=l+r>>1; if(w<=mid) change(k*2,l,mid,w,x); else change(k*2+1,mid+1,r,w,x); pushup(k); } inline int ask_max(int k,int l,int r,int z,int y){ if(l==z&&r==y) return p[k]; // printf("%d %d %d %d\n",l,r,z,y); int mid=l+r>>1; // printf("%d %d %d\n",l,r,mid);cin.get(); if(mid<z) return ask_max(k*2+1,mid+1,r,z,y); else if(y<=mid) return ask_max(k*2,l,mid,z,y); else return Max(ask_max(k*2,l,mid,z,mid),ask_max(k*2+1,mid+1,r,mid+1,y)); } }; int n,a[M],maxx=-1;; Xtree xt; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); xt.change(1,0,M,a[1],1); // printf("enter\n"); for(int i=2;i<=n;i++){ int w=xt.ask_max(1,0,M,0,a[i]-1); // printf("%d ",w); maxx=Max(maxx,w+1); xt.change(1,0,M,a[i],w+1); } printf("%d",maxx); return 0; }
這一次咱們的優化要從新設計狀態。
思考這樣一個問題,咱們可否使得咱們的合法序列爲有序的,從而能夠二分取尋找最優值?
咱們這樣來設計狀態:
設\(f_i\)表示以長度爲i的最長上升子序列的結尾的數值(不是下標)的最小值。
首先關注一下這個f數組的有序性。
咱們發現這個f數組必定是一個單調遞增的序列,不然,若是存在一個\(f_i\)知足\(i<j\)且\(f_i>=f_j\),以\(f_j\)結尾的長度爲j的最長上升子序列,設它的第i項爲k,則以k結尾的長度爲i的最長上升子序列必定存在,緣由就是j比i要長,而且k比\(f_i\)更優,故必定是一個單調遞增序列。
那麼咱們怎麼來利用它的單調性呢?
咱們先考慮怎麼用先有的序列的每個元素去維護f數組。
咱們設a數組爲咱們要求的最長上升子序列的那個題目給出的數組。
設此時此刻該用\(a_i\)去更新維護咱們的f數組,設當前f數組的長度爲len,由f數組的定義能夠知道,那麼當前a數組的最長上升子序列爲len。
容易想到的是,若是\(f_len\)要小於\(a_i\)的話,那麼咱們能夠另\(f_{++len}=a_i\),更新當前最長上升子序列的最優值。
接下來最重要的問題,也是這個算法的主體,就是\(a_i\)可能能夠更新len之前f數組的值,容易想到,若是知足\(f_len\)要小於\(a_i\),那\(a_i\)就無法去更新其他的f值,若是不是這種狀況呢?
咱們思考一下,能夠得出如下結論:
若是\(f_q\)比\(a_i\)要小,那麼\(a_i\)就能夠接在它後面,去更新長度爲q+1的最長上升子序列的末尾值。
可是若是\(f_{q+1}\)要比\(a_i\) 要小的話,顯然,雖然\(a_i\)知足條件,可是卻不可以更新\(f_{q+1}\)。
那何時\(a_i\)才能更新呢?
當且僅當\(a_i\)比\(f_q\)要大而且\(a_i\)要比\(f_{q+1}\)要小!
又由於f數組是一個單調上升的數組。
因此咱們能夠在f數組裏二分。
這裏二分推薦使用lower_bound和upper_bound,這兩個函數不只在STL裏有,其他時候也能夠用。只是我習慣打不少不少的頭文件,所以不知道這兩個函數在哪一個頭文件裏。。。算了,無傷大雅。
順便說一句。前者返回的是第一個大於等於的值,後者返回的是第一個大於的值。
具體用法看代碼,upper_bound相似
其實也能夠本身打一個二分查找,二分不算太難。
代碼:數組
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<sstream> #include<queue> #include<map> #include<vector> #include<set> #include<deque> #include<cstdlib> #include<ctime> #define dd double #define ll long long #define ull unsigned long long #define N 100010 #define M number using namespace std; int a[N],f[N],n; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); int len=1; f[len]=a[1]; for(int i=2;i<=n;i++){ if(a[i]>f[len]) f[++len]=a[i]; else{ int w=lower_bound(f+1,f+len+1,a[i])-f; f[w]=a[i]; } } printf("%d",len); }
其實求最長不降低子序列的方法相似,讀者不妨本身去推一下。這裏只放代碼
線段樹:函數
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<sstream> #include<queue> #include<map> #include<vector> #include<set> #include<deque> #include<cstdlib> #include<ctime> #define dd double #define ll long long #define ull unsigned long long #define N 400100 #define M 100100 using namespace std; inline int Max(int a,int b){ return a>b?a:b; } struct Xtree{//point_change square_ask no_lazy int p[N<<3]; inline void pushup(int k){ p[k]=Max(p[k*2],p[k*2+1]); } inline void change(int k,int l,int r,int w,int x){ if(l==r&&r==w){ p[k]=x; return; } int mid=l+r>>1; // printf("%d %d %d %d\n",l,r,mid,w);cin.get(); if(w<=mid) change(k*2,l,mid,w,x); else change(k*2+1,mid+1,r,w,x); pushup(k); } inline int ask_max(int k,int l,int r,int z,int y){ if(l==z&&r==y) return p[k]; int mid=l+r>>1; if(y<=mid) return ask_max(k*2,l,mid,z,y); else if(mid<z) return ask_max(k*2+1,mid+1,r,z,y); else return Max(ask_max(k*2,l,mid,z,mid),ask_max(k*2+1,mid+1,r,mid+1,y)); } }; Xtree xt; int n,a[N],b[N],maxx; int main(){ // freopen("dp.out","r",stdin); // freopen("1.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); xt.change(1,0,M<<1,a[1],1); for(int i=2;i<=n;i++){ int w=xt.ask_max(1,0,M<<1,0,a[i]); maxx=Max(maxx,w+1); xt.change(1,0,M<<1,a[i],w+1); } printf("%d\n",n-maxx); }
二分:優化
f[1]=a[1];int len=1; for(int i=2;i<=tail;i++) { if(a[i]<=f[len]) f[++len]=a[i]; else { int l=1,r=len; while(l<r) { int mid=l+r>>1; if(f[mid]<a[i]) r=mid; else l=mid+1; } f[l]=a[i]; } }
這裏手寫了一個二分,主要是由於作導彈攔截的時候還不會lower_bound和upper_bound,那時候的碼風也和如今不一樣。ui
接下來的主要介紹將最長公共子序列轉化爲最長上升子序列來求解。spa
咱們設第一個序列爲\(a_1,a_2,...a_n\),第二個序列爲\(b_1,b_2,...b_m\)設計
接下來的操做是,對a中的元素從小到大排序,變成\(a_i,a_j,...a_k\),a數組排序先後的兩個不一樣的數組之間設置一個映射關係,即有 \(a_1\rightarrow a_i,a_2\rightarrow a_j,...a_n\rightarrow a_k\),b中屬於a中的元素,則也作此類映射,同時b中不屬於a的元素所有去掉,程序中實現時只須要標記一下。code
這裏須要再加上一個操做,映射後,若是a中有q個x元素,b中有p個,若是p比q小,那麼要把着p個元素減到q個。否則結果會出錯。
這樣在映射後,a數組變成了一個不降低的序列,只須要對b數組跑一個最長不降低子序列便可,由於在映射後,b中任何一個不降低子序列都是與a的一個公共子序列。
洛谷上的題目:https://www.luogu.com.cn/problem/P1439
這個題目有必定的特殊性,由於都是1到n的一個全排列,因此去重操做就不用了。
\(O(nm)\) 作法
#include<iostream> using namespace std; int dp[1001][1001],a1[2001],a2[2001],n,m; int main() { //dp[i][j]表示兩個串從頭開始,直到第一個串的第i位 //和第二個串的第j位最多有多少個公共子元素 cin>>n>>m; for(int i=1;i<=n;i++)scanf("%d",&a1[i]); for(int i=1;i<=m;i++)scanf("%d",&a2[i]); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { dp[i][j]=max(dp[i-1][j],dp[i][j-1]); if(a1[i]==a2[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1); //由於更新,因此++; } cout<<dp[n][m]; }
\(O(nlogn)\)作法
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<sstream> #include<queue> #include<map> #include<vector> #include<set> #include<deque> #include<cstdlib> #include<ctime> #define dd double #define ll long long #define ull unsigned long long #define N 100010 #define M number using namespace std; int n; int a[N],b[N]; int m[N],f[N]; inline int Min(int a,int b) { return a>b?b:a; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); m[a[i]]=i; } for(int i=1;i<=n;i++) { scanf("%d",&b[i]); b[i]=m[b[i]]; f[i]=0x7ffffff; } f[1]=b[1];int len=1; for(int i=2;i<=n;i++) { int l=1,r=len; if(b[i]>f[len]) f[++len]=b[i]; else { while(l<r) { int mid=l+(r-l>>1); if(f[mid]>b[i]) r=mid; else l=mid+1; } f[l]=Min(f[l],b[i]); } } printf("%d",len); return 0; }
引用