解题报告 B3647 最长上升子序列

前言:

我是蒟蒻请多指教

B3647 最长上升子序列

题目描述

这是一个简单的动规板子题。

给出一个由 n(n≤5000) 个不超过 10^6 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。

最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。

朴素版本:

进行动态规划时首先要先明白状态时如何转移的,观察样例我们可以发现每一个数字对于它的最长上升子序列是由他前面的数字的长度在累加上它的状态完成的即:

f[i]=f[j]+1

我们只需要在i之前找到f[j]的最大值(此时发f[j]需要小于a[i])在加上当前的数字就可以了

for(int j=i-1;j>=0;j--)
{
    if(a[i]>a[j] && cnt<f[j])
	{
			t=j;
			cnt=f[j];
	}
}

枚举法寻找符合要求的数

f[i]=f[t]+1;

连接长度

for(int i=1;i<=n;i++)
{
	ans=std::max(ans,f[i]);
}

但我们仍要注意在最后寻找最大值

AC代码:

#include<bits/stdc++.h>
#define N 5010
int f[N],a[N],n;int main(){
	std::cin>>n;
	for(int i=1;i<=n;i++)
	{
		std::cin>>a[i];
	}
	int t,cnt=0;
	for(int i=1;i<=n;i++)
	{
		cnt=-1;
		for(int j=i-1;j>=0;j--)
		{
			if(a[i]>a[j] && cnt<f[j])
			{
				t=j;
				cnt=f[j];
			}
		}
		f[i]=f[t]+1;
	}
	int ans=-1;
	for(int i=1;i<=n;i++)
	{
		ans=std::max(ans,f[i]);
	}
	std::cout<<ans;
	return 0;
}

优化版本(二分):

在朴素版本的基础上,我们不难发现我们可以使用二分的方式来进行时间优化,枚举寻找最大值过于浪费时间了,如果我们定义一个数组d并将长度为i的最小数存入d[i]中,然后在最后找到最大长度求解,我们便可以节省很多的时间

首先是二分:

while(l<r)
{
	mid=(l+r+1)/2;
	if(a[i]>d[mid])
	    l=mid;
	else
	r=mid-1;
}

这一部分的操作是不断寻找上述符合要求的数,需要注意的是

mid=(l+r+1)/2;

加1的原因是防止我们的二分死循环,并注意r+1才是我们真正的赋值对象

d[r+1]=a[i];

对于新的数字进行更新,保证这是最小值,只有这样,在下一次找到了一个更大的数字时我们才能进行新的更新

len=std::max(len,r+1);

不断寻找最大值;

与朴素版的代码功能一样

for(int i=1;i<=n;i++)
{
	ans=std::max(ans,f[i]);
}

AC代码:

#include<bits/stdc++.h>
#define N 5010
int d[N],a[N];int n;
int main(){
	std::cin>>n;
	for(int i=1;i<=n;i++)
	{
		std::cin>>a[i];
	}
	int len=0;
	for(int i=1;i<=n;i++)
	{	
		int l=0,r=len,mid;
		while(l<r)
		{
			mid=(l+r+1)/2;
			if(a[i]>d[mid])
				l=mid;
			else
				r=mid-1;
		}
		len=std::max(len,r+1);
		d[r+1]=a[i];
	}
	std::cout<<len;
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值