最长上升子序列模型
(1)朴素版本(时间复杂度O(n^2))
#include <bits/stdc++.h>
#define MAX 0x3f3f3f3f
using namespace std;
const int maxn = 1e3+10;
int n,a[maxn],dp[maxn];//dp[i]表示长度为i的数组中最长上升子序列长度是多少
int main(){
scanf("%d",&n);//数组里元素个数
for(int i=1; i<=n; i++) scanf("%d",&a[i]);//输入数组
for(int i=1; i<=n; i++){
dp[i]=1;//每个位置初始化为1,因为开始更新时只有它一个元素
for(int j=1; j<i; j++){
//只要前面有比a[i]小的元素a[j],那就更新j点的长度以及利用j点长度更新i点长度
if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1);
}
}
//找最长上升子序列的长度
int res=-MAX;
for(int i=1; i<=n; i++) res=max(res,dp[i]);
printf("%d",res);
return 0;
}
(2)二分+贪心版本(时间复杂度O(nlog(n)))
核心思想:
利用二分进行维护最长上升子序列,但是要注意的是,这个最长上升子序列是不一定正确的。
举例好懂一点:
我们再举一个例子:有以下序列A[ ] = 3 1 2 6 4 5 10 7,求LIS长度。
我们定义一个B[ i ]来储存可能的排序序列,len 为LIS长度。我们依次把A[ i ]有序地放进B[ i ]里。(为了方便,i的范围就从1~n表示第i个数)
A[1] = 3,把3放进B[1],此时B[1] = 3,此时len = 1,最小末尾是3
A[2] = 1,因为1比3小,所以可以把B[1]中的3替换为1,此时B[1] = 1,此时len = 1,最小末尾是1
A[3] = 2,2大于1,就把2放进B[2] = 2,此时B[ ]={1,2},len = 2
同理,A[4]=6,把6放进B[3] = 6,B[ ]={1,2,6},len = 3
A[5]=4,4在2和6之间,比6小,可以把B[3]替换为4,B[ ] = {1,2,4},len = 3
A[6] = 5,B[4] = 5,B[ ] = {1,2,4,5},len = 4
A[7] = 10,B[5] = 10,B[ ] = {1,2,4,5,10},len = 5
A[8] = 7,7在5和10之间,比10小,可以把B[5]替换为7,B[ ] = {1,2,4,5,7},len = 5
最终我们得出LIS长度为5,但是,但是!!!B[ ] 中的序列并不一定是正确的最长上升子序列。在这个例子中,我们得到的1 2 4 5 7 恰好是正确的最长上升子序列,下面我们再举一个例子:有以下序列A[ ] = 1 4 7 2 5 9 10 3,求LIS长度。
A[1] = 1,把1放进B[1],此时B[1] = 1,B[ ] = {1},len = 1
A[2] = 4,把4放进B[2],此时B[2] = 4,B[ ] = {1,4},len = 2
A[3] = 7,把7放进B[3],此时B[3] = 7,B[ ] = {1,4,7},len = 3
A[4] = 2,因为2比4小,所以把B[2]中的4替换为2,此时B[ ] = {1,2,7},len = 3
A[5] = 5,因为5比7小,所以把B[3]中的7替换为5,此时B[ ] = {1,2,5},len = 3
A[6] = 9,把9放进B[4],此时B[4] = 9,B[ ] = {1,2,5,9},len = 4
A[7] = 10,把10放进B[5],此时B[5] = 10,B[ ] = {1,2,5,9,10},len = 5
A[8] = 3,因为3比5小,所以把B[3]中的5替换为3,此时B[ ] = {1,2,3,9,10},len = 5
最终我们得出LIS长度为5。但是,这里的1 2 3 9 10很明显并不是正确的最长上升子序列。因此,B序列并不一定表示最长上升子序列,它只表示相应最长子序列长度的排好序的最小序列。这有什么用呢?我们最后一步3替换5并没有增加最长子序列的长度,而这一步的意义,在于记录最小序列,代表了一种“最可能性”,只是此种算法为计算LIS而进行的一种替换。假如后面还有两个数据12和15,那么B[ ]将继续更新。
#include <bits/stdc++.h>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
int a[maxn];
int low[maxn];//存的是长度为i结尾的子序列中最大值
int n,cnt;//cnt为最长子序列长度
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin >> n;
for(int i=1; i<=n; i++) cin >> a[i];
low[++cnt]=a[1];//初始化第一个位置
for(int i=2; i<=n; i++){
//如果大于最长上升子序列中最大值,那就把他接在后面,同时最长上升子序列长度+1
if(a[i]>low[cnt]) low[++cnt]=a[i];
//否则,利用二分法找到最长上升子序列中第一个大于等于a[i]的元素并进行修改。
else{
low[lower_bound(low+1,low+1+cnt,a[i])-low]=a[i];
}
}
cout << cnt;
return 0;
}
还有一种是树状数组的方式,但时间复杂度和二分一样,所以就先不展示了。