咱们设状态\(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; }
引用