nyist oj 214 单调递增子序列(二) (动态规划经典)

本文介绍了一种求解最长递增子序列的有效算法,针对大规模数据集优化了时间复杂度,采用二分搜索更新一维数组dp,实现O(nlogn)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单调递增子序列(二)

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 4
 

描述

给定一整型数列{a1,a2...,an}(0<n<=100000),找出单调递增最长子序列,并求出其长度。

如:1 9 10 5 11 2 13的最长单调递增子序列是1 9 10 11 13,长度为5。

 

输入

有多组测试数据(<=7)
每组测试数据的第一行是一个整数n表示序列中共有n个整数,随后的下一行里有n个整数,表示数列中的所有元素.每个整形数中间用空格间隔开(0<n<=100000)。
数据以EOF结束 。
输入数据保证合法(全为int型整数)!

输出

对于每组测试数据输出整形数列的最长递增子序列的长度,每个输出占一行。

样例输入

7
1 9 10 5 11 2 13
2
2 -1

样例输出

5
1

来源

[521521]改编

这个题和前面做的那个单调递增子序列题目意思是一样的,但是这个题目数据量比较大,用前面的那种方法就会超时,前面的那一种方法dp[i]:用来储存以ai为末尾的最长递增子序列的长度;时间复杂度是o(n2),这个题目的数据范围到了100000,用这种方法就会超时;

所以我们采用另一种方法,用dp[i]:储存长度为i+1的递增序列中末尾元素的最小值;

 

建立一个一维数组dp[ ],用dp[i]保存长度为i的单调子序列的末尾元素的值,用top保存单调子序列的最大长度。

初始top=1,dp[1]=a1;

然后,我们从前向后遍历序列An(i : 2~n)。显然,我们会遇到两种情况:

1.a[i] > dp[top]。此时,我们把a[i]加入到长度为top的单调序列中,这时序列长度为top+1,top值也跟着更新,即dp[++top] = a[i];

2.a[i]<=dp[top]。此时,a[i]不能加入长度为top的单调序列,那它应该加入长度为多少的序列呢?

做法是,从后向前遍历dp[ ] (j: top-1~1),直到满足条件 a[i] > dp[j],此时,类似于步骤1,我们可以把a[i]加入到长度为j的单调序列中,这时此序列长度为j+1;(这段话转载于http://www.cnblogs.com/mycapple/archive/2012/08/22/2651453.html

 

如果直接这样做的话,时间复杂度也会达到o(n2),但是这里还可以优化,我们在遍历更新的时候可以采用更高效率的方法;这里我们可以采用二分搜索,对前面的a[i]进行更新;这种方法的时间复杂度就是o(nlogn)

 

下面是代码:

 

 

#include <cstdio>
#include <cstring>
const int maxn=100001;
int dp[maxn],a[maxn];
int Binary_search(int len,int k)//二分搜索
{//查找比第一个比dp[i]小或者是相等的位置
    int start,end,mid;
    start=1;
    end=len;
    while(start<=end)
    {
        mid=(start+end)>>1;
        if(k==dp[mid])
            return mid;
        if(k>dp[mid])
            start=mid+1;
        else
            end=mid-1;
    }
    return start;
}
int main()
{
    int n,i,t,len;
    while(scanf("%d",&n)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        for(i=0;i<n;i++)
            scanf("%d",&a[i]);
         len=1;
         dp[1]=a[0];//这里要从1开始
         for(i=1;i<n;i++)
         {
             t=Binary_search(len,a[i]);//通过二分搜索查找a[i]要插入的位置
             dp[t]=a[i];//更新dp的值
             if(t>len)//如果插入的位置在最后,更新最大的长度
                len=t;
         }
         printf("%d\n",len);
    }
}


下面是最优代码,好简短的:用了一个STL里面的lower_bound函数,这个函数就是二分查找,返回第一个比val小的位置,把这个函数使用熟练也会比较方便,与此相对的还有一个upper_bound函数;http://blog.csdn.net/niushuai666/article/details/6734403  这个内容可以参考这篇博客

 

 

 

 
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX=100100;
int num[MAX],top=0;
int main()
{
	int n;
	while(~scanf("%d",&n))
	{
		scanf("%d",&num[0]);
		top=1;
		for(int i=1;i!=n;i++)
		{
			scanf("%d",&num[i]);
			int * p=lower_bound(num,num+top,num[i]);
			if(p-num==top) ++top;
			*p=num[i];
		}
		printf("%d\n",top);
	}
	
}        

 

 

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值