关于 LCS 和 LIS

LCS (最大公共子序列)

朴素做法(n*n)

直接遍历全部的数组,遍历全部的状态,如果状态符合要求,就进行更新,否则进行继承。

#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];
}

优化做法 (nlogn)

使用最大上升子序列的方法来做。

将b数组里的数用a中的位置来表示。

a b c d e

c b a d e

如果b数组中的数满足上升的话,即为a中的子序列,因为a中子序列恒定为上升子序列。

#include<iostream>
#include<cstdio>
using namespace std;
int a[100001],b[100001],map[100001],f[100001];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){scanf("%d",&a[i]);map[a[i]]=i;}
	for(int i=1;i<=n;i++){scanf("%d",&b[i]);f[i]=0x7fffffff;}
	int len=0;
	f[0]=0;
	for(int i=1;i<=n;i++)
	{
		int l=0,r=len,mid;
		if(map[b[i]]>f[len])f[++len]=map[b[i]];
		else 
		{
		while(l<r)
		{	
		    mid=(l+r)/2;
		    if(f[mid]>map[b[i]])r=mid;
			else l=mid+1; 
		}
		f[l]=min(map[b[i]],f[l]);
     	}
    }
    cout<<len;
    return 0
}

LIS(最大上升子序列)

朴素做法(n*n)

	for(int i=1;i<=n;i++)
	{
		dp[i]=1;//初始化 
		for(int j=1;j<i;j++)//枚举i之前的每一个j 
		if(data[j]<data[i] && dp[i]<dp[j]+1)
		//用if判断是否可以拼凑成上升子序列,
		//并且判断当前状态是否优于之前枚举
		//过的所有状态,如果是,则↓ 
		dp[i]=dp[j]+1;//更新最优状态 
		
	}

优化做法(nlogn)

利用二分来进行优化,每次二分找到不小于其的第一个元素,并替换它。

int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		f[i]=0x7fffffff;
		//初始值要设为INF
		/*原因很简单,每遇到一个新的元素时,就跟已经记录的f数组当前所记录的最长
		上升子序列的末尾元素相比较:如果小于此元素,那么就不断向前找,直到找到
		一个刚好比它大的元素,替换;反之如果大于,么填到末尾元素的下一个q,INF
                就是为了方便向后替换啊!*/ 
	}
	f[1]=a[1];
	int len=1;//通过记录f数组的有效位数,求得个数 
	/*因为上文中所提到我们有可能要不断向前寻找,
	所以可以采用二分查找的策略,这便是将时间复杂
    度降成nlogn级别的关键因素。*/ 
	for(int i=2;i<=n;i++)
	{
		int l=0,r=len,mid;
		if(a[i]>f[len])f[++len]=a[i];
		//如果刚好大于末尾,暂时向后顺次填充 
		else 
		{
		while(l<r)
		{	
		    mid=(l+r)/2;
		    if(f[mid]>a[i])r=mid;
	//如果仍然小于之前所记录的最小末尾,那么不断
	//向前寻找(因为是最长上升子序列,所以f数组必
	//然满足单调) 
			else l=mid+1; 
		}
		f[l]=min(a[i],f[l]);//更新最小末尾 
     	}
    }
    cout<<len;

 LIS 例题

P1091 [NOIP2004 提高组] 合唱队形

这题是可用朴素做法求两边最大上升子序列。

#include "bits/stdc++.h"
using namespace std;
int a[110];
int f[110],g[110];
int main()
{
	int n;
	cin>>n;
	
	for (int i=1;i<=n;i++){
		cin>>a[i];
	}
	//最多的人满足先升后降。
	
	for (int i=1;i<=n;i++){
		for (int j=0;j<i;j++){
			if(a[i]>a[j]){
				f[i]=max(f[i],f[j]+1);
			}
		}
	}
	a[n+1]=0;
	for (int i=n;i>=1;i--){
		for (int j=n+1;j>i;j--){
			if(a[i]>a[j]){
				g[i]=max(g[i],g[j]+1);
			}
		}
	}
	int ans=0;
	for (int i=1;i<=n;i++){
		ans=max(ans,f[i]+g[i]-1);
	}
	
	cout<<n-ans;
	
}

P1020 [NOIP1999 提高组] 导弹拦截

这是一道经典求最大上升子序列,和最大不上升子序列的题目。因为最大数据可达1e5 所以使用 nlogn 的做法求解。

#include <bits/stdc++.h>
using namespace std;
#define int long long 

int a[100010],b[100010];
int c[100010];
signed main()
{
	int cnt=0;
	int x;
	while(cin>>x){
		a[++cnt]=x;
	}
	
	int res=0;
	b[0]=1e9;
	for (int i=1;i<=cnt;i++){
		if(b[res]>=a[i]){
			b[++res]=a[i];;
		}
		else {
			int l=1,r=res;
			while(l<r){
				int mid=(l+r)>>1;
				
				if(b[mid]<a[i]){
					r=mid;
				}
				else {
					l=mid+1;
				}
			}
			b[l]=a[i];
			
		}
	}
	
	cout<<res<<endl;
	int m=0;
	c[0]=-2e9;
	for (int i=1;i<=cnt;i++){
		if(a[i]>c[m]){
			c[++m]=a[i];
		}
		else {
			int l=1,r=m;
			while(l<r){
				int mid=(l+r)>>1;
				
				if(c[mid]>=a[i]) r=mid;
				else l=mid+1;
			}
			
			c[l]=a[i];
			
		}
	}
	
	cout<<m<<endl;
	
	
	

}

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值