POJ-3903-最长上升子序列

题目就不多说了,链接–> POJ-3903-最长上升子序列

解法一

这个题目用动态规划来做是没问题的,大家一般都会想到这种解法:
D P [ i ] : 表 示 下 标 从 0 到 i 的 序 列 的 最 长 上 升 子 序 列 长 度 DP[i]:表示下标从0到i的序列的最长上升子序列长度 DP[i]:0i
然后动态转移方程如下:

记第i位前的所有位置为j(到时候要遍历的),如果sequence[i]>sequence[j],则dp[i]=dp[j]+1,然后对所有j都考虑一下取最大的那个。

代码差不多会写成这个样子,然后AC不了,这里的复杂度是O(n^2),题目卡的就是这个O(n^2)

#include<iostream>
#include<algorithm>
using namespace std;
#define MAX_L 100000

int sequence[MAX_L];
int dp[MAX_L];
int main()
{
	int n;
	while(cin >> n)
	{
		// 清空数组
		memset(sequence,0,sizeof(sequence));
		memset(dp, 0, sizeof(dp));
		// 输入数据
		for (int j = 0;j < n;j++)
		{
			cin>>sequence[j];
			dp[j]=1;  // 先全部初始化为1
		}
		for (int i = 0;i < n;i++)
		{
			for (int j = 0;j < i;j++)
			{
				if(sequence[i]>sequence[j])
				{
					dp[i]=max(dp[i],dp[j]+1);
				}
			}
		}
		// 找出结果的最大值
		int res=-1;
		for (int i = 0;i < n;i++)
		{
			res=max(res,dp[i]);
		}
		cout<< res <<endl;
	}
	
	return 0;
}

解法二

然后我看到书上有另一种巧妙的办法,愣是看了半小时才搞懂,它把DP的记录的东西变了一下,下面我结合自己的理解稍加修改后放出来,如下:
d p [ i ] : 所 有 长 度 为 i 的 上 升 子 序 列 中 , 结 尾 最 小 的 那 个 序 列 的 结 尾 数 字 ( 多 读 几 遍 。 。 。 ) dp[i]:所有长度为i的上升子序列中,结尾最小的那个序列的结尾数字 (多读几遍。。。) dp[i]:i
!!!(dp下标从1开始)!!!,这里从1开始,也会更加便于理解

我先举个例子来说明,然后再给出怎么转移的,现在假设有5个数字,分别为{4,2,3,1,5},好的,我们一个一个数字来考虑:

  1. 按照上述dp的含义,最开始,dp[1]就是4了,没问题吧?(dp下标从1开始,别忘了)

  2. 然后数字2来了,2<4,那么dp[1]应该更新为2了,这个时候dp[2]也没办法知道,也根本不存在dp[2]

  3. 然后3来了,2<3<4,于是 4 2 3这样的序列中,dp[1]还是2不变,而所有长度为2的上升子序列中(这里只有2 3),结尾最小的那个序列是什么? 因为只有 2 3序列, 所以dp[2]就是3了。这是我们人工列举出来的结果。
    细想一下,我们之前求得dp[1]=2,那么现在来的数字3,比它大,也就是说,现在可以有长度为2的上升子序列了,这不就是我们刚才列举出来的dp[2]吗?对的,所以以后每次新来一个数字时,我们都去已经求解的dp[]数组中找一个比当前数字小的值(这个值可能会有多个,取最大的那个),记为dp[i],那么我们下一步就是要更新 d p [ i + 1 ] = m i n ( d p [ i + 1 ] , a j ) dp[i+1]=min(dp[i+1],a_j) dp[i+1]=min(dp[i+1],aj)

  4. 然后1来了,按照步骤3中的说法,好像找不到一个满足条件的dp[i]啊? 没关系,找不到就算了。但是每次新来一个数的时候,都要和dp[1]去比一下,因为1个孤零零的数字自己也是一个上升子序列,只不过长度为1嘛。也就是 d p [ 1 ] = m i n ( d p [ 1 ] , a i ) dp[1]=min(dp[1],a_i) dp[1]=min(dp[1],ai)

  5. 最后5来了,5可以找到dp[i]=3,其中i=2,然后按照步骤3,dp[i+1]即dp[3]=5。

初始化全为MAX_INT,上述过程如下图所示:
在这里插入图片描述

然后是这样的动态转移方程:

如果在之前求解的dp中,找到第一个dp[i],使得a[j]>dp[i],那么dp[i+1]=min(dp[i+1],a[j])。

当然,也要注意每次和dp[1]比较一下,因为每个值自己都是一个长度为1的上升子序列。(在代码中会发现其实并不需要这么干,因为使用的lower_bound()这个函数,具体看代码中的注释吧。)

#include<iostream>
#include<algorithm>
using namespace std;
#define MAX_L 100000

int sequence[MAX_L];
int dp[MAX_L+1];

int main()
{
	int n;
	while (cin >> n)
	{
		// 清空数组
		memset(sequence, 0, sizeof(sequence));
		fill(dp,dp+ MAX_L+1, INT_MAX);
		// 输入数据
		for (int j = 0;j < n;j++)
		{
			cin >> sequence[j];
		}
		for (int j =0;j < n;j++)
		{
			// 注意,这里我们是从1下标开始的,所以在指针的开始和结尾都加上了一个int的长度
			// 在这里用了lower_bound这个内置函数,它会二分查找第一个大于给定值的指针位置 (可以自己推导一下,dp是单调递增的)
			int* this_i_plus_1=lower_bound(dp+1, dp + MAX_L+1,sequence[j]);
			// 即动态转移方程的dp[i+1]=min(dp[i+1],a[j])部分
			*this_i_plus_1=min(*this_i_plus_1,sequence[j]);

			// 和长度为1的上升序列比较
			dp[1]=min(dp[1],sequence[j]);  // 这里这句话其实可以不要写,因为lower_bound没找到大于给定值时,会指向第一个,也就下面要比的dp[1]
		}
		// 找出结果的最大值
		// 先找到从下标1开始往后的第一个MAX_INT,也就是自从初始化以来从没改过的值
		int *max_value_pos =lower_bound(dp +1, dp + MAX_L+1, INT_MAX);
		// 再找到dp数组的头指针,这里从下标1 开始的
		int *head=dp+1;
		// 两个相减就是最长的上升子序列长度了
		cout << max_value_pos-head << endl;
	}

	return 0;
}

指针是真的强大,指哪打哪,真的好用啊!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值