最长上升子序列-LIS的长度

首先要知道什么是子序列?它和子串的区别:
子串是指字符串中连续的n个字符。
子序列是指字符串中不一定连续但先后顺序不变的n个字符。
对于一个给定的数组,它的最长上升子序列不一定唯一,但是最长上升子序列的长度一定是确定的。
这里我给出两种方法分别是dpO(n2)的和贪心+二分的算法。
(1)动态规划dp。
我们可以想想之所以可以用动态规划是因为动态规划的一个特点就是当前解可以由上一个阶段的解推出, 由此,把我们要求的问题简化成一个更小的子问题。子问题具有相同的求解方式,只不过是规模小了而已。最长上升子序列就符合这一特性。我们要求n个数的最长上升子序列,可以求前n-1个数的最长上升子序列,再跟第n个数进行判断。
所以我们可以设一个状态:f[i]表示以a[I]结尾的最长上升子序列的长度。
状态转移方程:f[i]=max(f[i],f[j]+1) 1<=j<i,1<=i<=n。
板子代码:

#include<iostream>
#include<cstdio>
#define maxn 1000
using namespace std;
int n;
int a[maxn];
int f[maxn];
int main(){
 cin>>n;
 for(int i=1;i<=n;i++)f[i]=1;
 for(int i=1;i<=n;i++)cin>>a[i];
 int ans=0;
 for(int i=1;i<=n;i++){
  for(int j=1;j<i;j++){
   if(a[i]>a[j])f[i]=max(f[i],f[j]+1);//如果要求最长不下降子序列的长度就是>=,
                            //如果要求最长下降子序列的长度就是<.
  }
  ans=max(ans,f[i]);
 }
 cout<<ans<<endl;
 return 0;
} 
/*
8
389 207 155 300 299 170 158 65
*/

(2)贪心+二分
新建一个ans[]数组,用来记录当前的“最长上升子序列”,因为对于一个上升子序列,显然其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长。因此我们采用这样的方法来维护ans数组:对于每一个a[ i ],如果a[ i ] > ans [当前最长的LIS长度],就把 a [ i ]接到当前最长的LIS后面,否则就需要用 a [ i ] 取更新 ans数组。具体方法是,在ans数组中找到第一个大于等于a [ i ]的元素ans[ id ],用a [ i ]去更新 ans [id](这里就是用到了贪心的思想)。至于找第一个大于等于a [ i ]的元素ans[ id]我们就用到了二分,因为数组ans是有序的(上升的)。
板子代码:

#include<iostream>
#include<cstdio>
#define maxn 100010
using namespace std;
int n;
int a[maxn];
int ans[maxn];
int fen(int cnt,int x){
 int l=1;
 int r=cnt;
 while(l<=r){
  int mid=(l+r)>>1;
  if(ans[mid]<x)l=mid+1;
  else r=mid-1;
 }
 return l;
}
int main(){
 cin>>n;
 for(int i=1;i<=n;i++)cin>>a[i];
 ans[1]=a[1];
 int cnt=1;
 for(int i=2;i<=n;i++){
  if(a[i]>ans[cnt])ans[++cnt]=a[i];
  else{
   int id=fen(cnt,a[i]);//找到第一个大于等于a[i]的值得下标
   ans[id]=a[i];
  }
 }
 cout<<cnt<<endl;
 return 0;
} 

最后提醒一下,结果ans[]数组中的元素并不一定是最长上升子序列,只是长度是最长上升子序列的长度,因为它可能有后面的元素去替换前面的元素,但是子序列要求元素的顺序是不能变的,所以它不一定是最长上升子序列。
看个例题:
https://www.luogu.com.cn/problem/P1020
这一题用O(n2)的dp算法只能过一半,所以可以用贪心+二分的方法。
AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int f[100000],h[100000];
int a[100000];
int main(){
	int c,n=0;
	int len1=0,len2=0;
	while(cin>>c){
		n++;
		a[n]=c;
	//	cout<<a[cnt]<<endl;
	}
	len1=len2=1;
	f[len1]=a[1];h[len2]=a[1];
	for(int i=2;i<=n;i++){
		if(f[len1]>=a[i])f[++len1]=a[i];
		else{
			int id=upper_bound(f+1,f+len1+1,a[i],greater<int>())-f;
			f[id]=a[i];
		}
	}
	for(int i=2;i<=n;i++){
		if(h[len2]<a[i])h[++len2]=a[i];
		else{
			int id=lower_bound(h+1,h+len2+1,a[i])-h;
			h[id]=a[i];
		}
	}
	cout<<len1<<endl;
	cout<<len2<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值