最长上升子序列(LIS)是指一个序列中最长的单调递增的子序列,对于任意的i<j都满足ai<aj的子序列。
下面我们来介绍两种dp来求LIS。
方法1:
我们首先来建立一下递推关系:
定义dp[i]:为以ai为末尾的最长上升子序列的长度。
以 ai 结尾的上升子序列是:
(1)只包含 ai 的子序列
(2)在满足 j<i 并且 aj<ai 的以 aj 为结尾的上升子序列末尾,追加 ai 后得到的子序列
这二者之一。这样就可以建立如下递推关系:
dp[i]=max{1,dp[j]+1 j<i且 aj<ai };
代码如下:
#include<bits/stdc++.h> using namespace std; const int maxn=1000005; int dp[maxn]; int a[maxn]; int main() { int n; int res = -1; cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) { dp[i] = 1; for (int j = 1; j < i; j++) { if (a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1); } res=max(res,dp[i]); } cout << res; return 0; }
这个算法的复杂度为O(n2),当数据量过多时会超时,下面介绍复杂度为O(nlogn)的算法。
方法2:
首先我们来定义dp[k]:长度为k的上升子序列的最末元素,若有多个长度为k的上升子序列,则记录最小的那个最末元素。
注意dp中元素是单调递增的,我们在下面要用到这个性质。
len为LIS的长度,首先让len=1,dp[1]=a[1], 然后i从2开始,对于a[i],如果
(1) a[i]>dp[len],那么此时可以直接把a[i]接到d[len]的后面,并且长度加一。即d[++len]=a[i]。
(2) a[i]<dp[len],那么我们就在dp这个数组中找到第一个比a[i]大
的数,并用a[i]替换这个数,此时数组dp仍然保持递增且长度不变。
这样我们就维护了数组dp,并且最后的len就是最长上升子序列的长度。算法复杂度为O(nlogn)。
代码如下:
#include<bits/stdc++.h> using namespace std; const int maxn=1000005; int dp[maxn]; int a[maxn]; int main() { int n; cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; int len=1; dp[1]=a[1]; for (int i = 2; i <= n; i++) { if (a[i] > dp[len]) dp[++len]=a[i]; else{ int j=lower_bound(dp+1,dp+len+1,a[i])-dp; dp[j]=a[i]; } } cout<<len; return 0; }
下面再补充一个求最长不上升子序列的方法,复杂度同样为O(nlogn)。
代码如下:
#include<bits/stdc++.h> using namespace std; const int maxn = 1000005; int a[maxn], dp[maxn]; bool cmp(int a, int b) { return a > b; } int main() { int n; cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; int len = 1; dp[1] = a[1]; for (int i = 2; i <= n; i++) { if (dp[len] >= a[i]) dp[++len] = a[i]; else dp[upper_bound(dp + 1, dp + len + 1, a[i], cmp) - dp] = a[i]; } cout << len; return 0; }
这里由于dp数组里的数递减的,而upper_bound适用于递增数列,因此我们给他重载了比较函数。
这样当 a[i]>dp[len] 时,我们在dp数组中找到最后一个大于a[i]的数(因为非上升子序列,可能会存在相同的数,用upper比较好一点),并替换它。
如果dp里没有比a[i]大的数,就会返回第一个数,这时a[i]替换的就是dp的第一个数,同样满足dp是递减的数组。
这样我们就得到了最长不上升子序列的长度len。