LIS LCS and LCIS
LIS:(最长上升子序列)
LIS:一个序列,求它的最长上升子序列的最大长度。
***关于LIS,一般有三种做法,先写下两种吧,一种就是O(n2)的DP算法;一种就是O(nlogn)的二分+贪心算法;还有一种也是O(nlogn)的树状数组的办法,先写一写前两种,以后再补吧。****
解法一:经典做法DP:
思路:
状态设计:dp[i]代表以a[i]结尾的LIS的长度
状态转移:dp[i]=max(dp[i], dp[j]+1) (1<=j< i, a[j]< a[i])
边界处理:dp[i]=1 (1<=i<=n)
时间复杂度:O(N^2)
代码模板:
for(int i=1;i<=n;++i){ dp[i]=1; for(int j=1;j<=i-1;++j){ if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1); } ans=max(ans,dp[i]); }
例题:最长上升子序列
Description 一个数的序列bi,当b1< b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1,a2,...,aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1<=i1 < i2 < ... < iK<=N。比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。这些子序列中最长的长度是4,比如子序列(1,3,5,8)。 你的任务,就是对于给定的序列,求出最长上升子序列的长度。 Input 输入的第一行是序列的长度N(1<=N<=1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。 Output 最长上升子序列的长度。 Sample Input 6 1 6 2 5 4 7 Sample Output 4
#include<bits/stdc++.h> using namespace std; int a[105],f[105],i,j,maxx,n; int main() { cin>>n; for(i=1; i<=n; i++) { cin>>a[i]; f[i]=1; } for(i=2; i<=n; i++) for(j=1; j<i; j++) if(a[j]<a[i]) { f[i]=max(f[j]+1,f[i]); } maxx=1; for(i=1; i<=n; i++) if(f[i]>f[maxx])maxx=i; cout<<f[maxx]<<endl; }
解法二:贪心加二分
思路:新建一个 low 数组,low [ i ]表示长度为i的LIS结尾元素的最小值。对于一个上升子序列,显然其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长。因此,我们只需要维护 low 数组。
那么,怎么维护 low 数组呢?
对于每一个a [ i ],如果a [ i ]能接到 LIS 后面,就接上去;否则,就用 a [ i ] 取更新 low 数组。具体方法是,在low数组中找到第一个大于等于a [ i ]的元素low [ j ],用a [ i ]去更新 low [ j ]。如果从头到尾扫一遍 low 数组的话,时间复杂度仍是O(n^2)。我们注意到 low 数组内部一定是单调不降的,所有我们可以二分 low 数组,找出第一个大于等于a[ i ]的元素。二分一次 low 数组的时间复杂度的O(lgn),所以总的时间复杂度是O(nlogn)。
有以下序列A[ ] = 3 1 2 6 4 5 10 7,求LIS长度:
- 3 A:3 B:3 len=1
- 1 A:3 1 B:1 len=1;
- 2 A:3 1 2 B:1 2 len=2
- 6 A:3 1 2 6 B:1 2 6 len=3
- 4 A:3 1 2 6 4 B:1 2 4 len=3
- 5 A:3 1 2 6 4 5 B:1 2 4 5 len=4
- 10 A:3 1 2 6 4 5 10 B:1 2 4 5 10 len=5
- 7 A:3 1 2 6 4 5 10 7 B:1 2 4 5 7 len=5
因为在B中插入的数据是有序的,不需要移动,只需要替换,所以可以用二分查找插入的位置
代码模板:
int binary_search(int r,int x) { int L=1,mid; while(L<=R){ mid=(L+R)/2;//mid=(L+R)>>1; if(a[mid]<=x) L=mid+1; else R=mid-1; } return L; }
主函数: for(int i=1;i<=n;i++){ cin>>a[i];Low[i]=inf;} Low[1]=a[1];ans=1; for(int i=2;i<=n;i++) { if(a[i]>Low[ans]) Low[++ans]=a[i]; else Low[binary_search(ans,a[i])]=a[i]; } cout<<ans<<endl;
这其中用到了二分查找第一个大于等于的,其实C++里面的有一个函数可用代替二分,那就是 —— lower_bound( )函数。
lower_bound( )函数:返回值是指向大于等于key的值的位置
upper_bound()函数:返回值是大于key的值的位置
对象:有序数组或容器
例如:a[]={1,2,3,4,5}
cout<<lower_bound(a,a+5,3)-a;输出2
cout<<upper_bound(a,a+5,3)-a;输出3
利用lower_bound()函数的做法
fill(Low,Low+n,inf); int ans=-1; for(int i=1;i<=n;i++){ int j=lower_bound(Low+1,Low+1+n,a[i])-Low; d[i]=j; ans=max(ans,d[i]); Low[j]=a[i]; } cout<<ans<<endl;
这里主要注意一下lower_bound函数的应用,注意减去的Low是地址:地址 - 地址 = 下标
解法三:树状数组
https://blog.csdn.net/lxt_Lucia/article/details/81206439?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1
LIS变形(带指定项的最长子序列):
Description LIS 问题是最经典的动态规划基础问题之一。如果要求一个满足一定条件的最长上升子序列,你还能解决吗? 给出一个长度为 N 整数序列,请求出它的包含第 K个元素的最长上升子序列。 例如:对于长度为 6的序列< 2,7,3,4,8,5 >,它的最长上升子序列为 <2 ,3,4,5 > ,但如果限制一定要包含第 2个元素,那么满足此要求的最长上升子序列就只能是 <2,7,8 >了。 Input 第一行为两个整数 N,K,如上所述。 接下来是N个整数,描述一个序列。 Output Sample Input 8 6 65 158 170 299 300 155 207 389 Sample Output 4 Hint 【数据范围】 对于60%的数据,满足 0 < n < = 4000,0 < k < = n 对于100%的数据,满足 0 < n < = 200000,0 < k < = n
#include <bits/stdc++.h> using namespace std; const int N=200005; int f[N],a[N],d[N]; int n,k,ans,num; int main() { cin>>n>>k; for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<k;i++) if(a[i]>=a[k]) a[i]=-1; for(int i=1;i<=n;i++) { if(a[i]==-1) continue; if(i>k&&a[i]<=a[k]) continue;//思路就是删掉k前面的大于k的,以及k后面小于k的 if(a[i]>d[num]) { d[++num]=a[i];continue; } int j=lower_bound(d+1,d+num,a[i])-d; d[j]=a[i]; } cout<<num<<endl; return 0; }
LCS:最长公共子序列
最长公共子序列,英文缩写为LCS(Longest Common Subsequence)。其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。
动态规划:
LCS(x,y) =
(1) LCS(x - 1,y - 1) + 1 如果Ax = By
(2) max(LCS(x – 1, y) , LCS(x, y – 1)) 如果Ax ≠ By
(3) 0 如果x = 0或者y = 0
例题:
Description 一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列X= < x1, x2,…, xm>,则另一序列Z= < z1, z2,…, zk>是X的子序列是指存在一个严格递增的下标序列 < i1, i2,…, ik>,使得对于所有j=1,2,…,k有 Xij=Zj 例如,序列Z=是序列X=的子序列,相应的递增下标序列为<2,3,5,7>。给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。例如,若X= < A, B, C, B, D, A, B>和Y= < B, D, C, A, B, A>,则序列是X和Y的一个公共子序列,序列也是X和Y的一个公共子序列。而且,后者是X和Y的一个最长公共子序列,因为X和Y没有长度大于4的公共子序列。 给定两个序列X= < x1, x2, …, xm>和Y= < y1, y2, … , yn>,要求找出X和Y的一个最长公共子序列。 Input 共有两行。每行为一个由大写字母构成的长度不超过200的字符串,表示序列X和Y。 Output 输出仅为一个非负整数。表示所求得的最长公共子序列的长度。若不存在公共子序列.则输出0。 Sample Input ABCBDAB BDCABA Sample Output 4 Hint 最长公共子串(Longest Common Substirng)和最长公共子序列(Longest Common Subsequence,LCS)的区别为:子串是串的一个连续的部分,子序列则是从不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列;也就是说,子串中字符的位置必须是连续的,子序列则可以不必连续。字符串长度小于等于1000。
#include <bits/stdc++.h> using namespace std; const int N=5005; int f[N][N]; int maxx=0; int sum=0; int main() { string s,t; cin>>s; cin>>t; int lens=s.length(),lent=t.length(); for(int i=1;i<=lens;i++) { for(int j=1;j<=lent;j++) { f[i][j]=max(f[i-1][j],f[i][j-1]); if(s[i-1]==t[j-1]) f[i][j]=max(f[i][j],f[i-1][j-1]+1); } } cout<<f[lens][lent]<<endl; return 0; }
LCIS:最长公共上升子序列
给出有 n 个元素的数组 a[] , m 个元素的数组 b[] ,求出它们的最长上升公共子序列的长度.
Description 给定两个整数序列,写一个程序求它们的最长上升公共子序列。 当以下条件满足的时候,我们将长度N的序列S1,S2,...,SN称为长度为M的序列A1,A2,...,AM的上升子序列: 存在1<= i1 < i2 <...< iN <= M,使得对所有 1 <= j <= N,均有Sj=Aij,且对于所有的1 <= j < N,均有Sj < Sj+1。 Input 每个序列用两行表示,第一行是长度M( 1<=M<=500),第二行是该序列的M个整数Ai( −2^31<=Ai<2^31) Output 在第一行,输出两个序列的最长上升公共子序列的长度L。在第二行,输出该子序列。如果有不止一个符合条件的子序列,则输出位置最靠前的一个。 Sample Input 5 1 4 2 5 -12 4 -12 1 2 4 Sample Output 2 1 4
确定状态
可以定义 dp[i][j] 表示以 a 数组的前 i 个整数与 b 数组的前 j 个整数且以 b[j] 为结尾构成的公共子序列的长度。
对于解决DP问题,第一步定义状态是很重要的!
需要注意,以 b[j] 结尾构成的公共子序列的长度不一定是最长公共子序列的长度!
状态转移方程:
① a[i] != b[j], dp[i][j] = dp[i-1][j]
② a[i] == b[j], dp[i][j] = max(dp[i-1][k]+1) (1 <= k <= j-1 && b[j] > b[k])
O(N * M)算法
分析:到这里好像没有更好的方法来优化查找了,那让我们再仔细的分析一下问题。
当a[i] != b[j], dp[i][j] = dp[i-1][j]
,这对问题并没有什么影响。
当a[i] == b[j]
,的时候 dp[i][j] = max(dp[i-1][k]+1) (1 <= k <= j-1 && b[j] > b[k])
,我们发现一个特点,其实 a[i] ( b[j] )与 b[k] 的关系早就在之前就可以确定了!( i 是最外层循环, j` 是内层循环,当 j` 遍历到 k 时,就足以判断 b[j] ? b[j`] 的大小关系了),因此我们只需要在内层循环与外层循环直接维护一个 max_dp 的值,等到 a[i] == b[j] 的时候,直接令 dp[i][j] = max_dp + 1
即可,时间复杂度降到了 O(N * M)。
5 | -12 | 1 | 2 | 4 | 5 | |
1 | 0 | 0 | 1 | 0 | 0 | 0 |
4 | 0 | 0 | 1 | 0 | 2 | 0 |
2 | 0 | 0 | 1 | 2 | 2 | 0 |
5 | 1 | 0 | 1 | 2 | 2 | 3 |
-12 | 1 | 1 | 1 | 2 | 2 |
3 |
for (int i = 1 ; i <= n ; ++i) { int max_dp = 0; for (int j = 1 ; j <= m ; ++j) { dp[i][j] = dp[i-1][j]; if (a[i] > b[j]&& max_dp < dp[i-1][j]) max_dp = dp[i][j]; else if (a[i] == b[j]) { dp[i][j] = max_dp + 1; } ans = max(ans, dp[i][j]); } }
将空间复杂度优化
int main() { scanf("%d",&t); while(t--) { scanf("%d%d",&n1,&n2); for(i=1;i<=n1;i++) scanf("%d",&a[i]); for(i=1;i<=n2;i++) scanf("%d",&b[i]); memset(f,0,sizeof(f)); for(i=1;i<=n1;i++) { max=0; for(j=1;j<=n2;j++) { if (a[i]>b[j]&&max<f[j]) max=f[j]; if (a[i]==b[j]) f[j]=max+1; } } max=0; for(i=1;i<=n2;i++) if (max<f[i]) max=f[i]; printf("%d\n",max); } }
例题:
/*************************************************** LCIS:最长公共上升子序列 求序列 A 长度为 N 和序列 B 长度为 M 的 LCS 序列下标从 1 开始 返回 LCS 长度 *****************************************************/ int dp[maxn]; int LCS(int n, int m) { memset(dp, 0, sizeof(dp)); for(int i = 1; i <= n; i++) { int tmp = 0; // 存 i 确定, 且 a[i] > b[j] 时最大的 dp[j] for(int j = 1; j <= m; j++) { if(a[i] > b[j] && dp[j] > tmp) tmp = dp[j]; else if(a[i] == b[j]) dp[j] = tmp+1; } } int ans = 0; for(int i = 1; i <= m; i++) ans = max(ans, dp[i]); return ans; }
hud 1159 Common Subsequence
Problem Description A subsequence of a given sequence is the given sequence with some elements (possible none) left out. Given a sequence X = <x1, x2, ..., xm> another sequence Z = <z1, z2, ..., zk> is a subsequence of X if there exists a strictly increasing sequence <i1, i2, ..., ik> of indices of X such that for all j = 1,2,...,k, xij = zj. For example, Z = <a, b, f, c> is a subsequence of X = <a, b, c, f, b, c> with index sequence <1, 2, 4, 6>. Given two sequences X and Y the problem is to find the length of the maximum-length common subsequence of X and Y. The program input is from a text file. Each data set in the file contains two strings representing the given sequences. The sequences are separated by any number of white spaces. The input data are correct. For each set of data the program prints on the standard output the length of the maximum-length common subsequence from the beginning of a separate line. Sample Input abcfbc abfcab programming contest abcd mnp Sample Output 4 2 0
#include <bits/stdc++.h> using namespace std; const int inf=0x3f3f3f3f; const int maxx=1005; int f[maxx][maxx]; int n,m; string s1,s2; int lcs() { memset(f,0,sizeof(f)); for(int i=1;i<=s1.length();i++) for(int j=1;j<=s2.length();j++){ if(s1[i-1]==s2[j-1]) f[i][j]=f[i-1][j-1]+1; else f[i][j]=max(f[i-1][j],f[i][j-1]); } return f[s1.length()][s2.length()]; } int main() { while(cin>>s1>>s2){ cout<<lcs()<<endl; } return 0; }
LCS:最长公共子串
最长公共子串(Longest Common Substirng)和最长公共子序列(Longest Common Subsequence,LCS)的区别为:子串是串的一个连续的部分,子序列则是从不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列;也就是说,子串中字符的位置必须是连续的,子序列则可以不必连续。字符串长度小于等于1000。
思路:就是有相等的时候,f[i][j]=f[i-1][j-1]+1;表示连续的时候才有赋值,如果不相等就等于0;
#include <bits/stdc++.h> using namespace std; const int N=5005; int f[N][N]; int ans=0; int main() { string s,t; cin>>s; cin>>t; int lens=s.length(),lent=t.length(); for(int i=1;i<=lens;i++) { for(int j=1;j<=lent;j++) { if(s[i-1]==t[j-1]) f[i][j]=f[i-1][j-1]+1; else f[i][j]=0; ans=max(ans,f[i][j]); } } cout<<ans<<endl; return 0; }