《最长递增子序列》题解

最长递增子序列 题解

题目

题目描述

给出一个数列,求其最长递增子序列.

数据范围

n < = 1 0 5 n <= 10^5 n<=105.

题解

思路

本题应考虑 DP,最朴素的想法便是设 d p [ i ] dp[i] dp[i] 为以 i i i 为结尾的最长子序列的长度,然后对于每一个 s [ i ] s[i] s[i],枚举每一个 j < = s [ i ] j<=s[i] j<=s[i] 的数据,取 d p [ s [ i ] ] = m i n ( d p [ s [ i ] , d p [ j ] + 1 ) dp[s[i]]=min(dp[s[i],dp[j]+1) dp[s[i]]=min(dp[s[i],dp[j]+1).

这样的效率是 O ( n 2 ) O(n^2) O(n2),不足以满足题目要求。

于是我们考虑将效率降为 O ( n l o g n ) O(nlogn) O(nlogn),我们分析数据:

对于某一个长度的子序列,其结尾的数字越小越优(相同的长度,结尾数字越小越可以接更多的数字),而对于相同结尾数字的子序列,其长度约长越优。因此出现了“两重单调性”,我们考虑用单调队列来解决(单调队列的下标为一重单调,数据为另一重单调)。

当我们尝试一个 s [ i ] s[i] s[i] 的时候,用二分查找 d p dp dp 数组中最大的比 s [ i ] s[i] s[i] 小的数(因为单调性,找到的数对应的下表——也就是子串的长度——一定是最长的),然后令 d p [ i ] = m i n ( d p [ i ] , s [ i ] ) dp[i]=min(dp[i],s[i]) dp[i]=min(dp[i],s[i]),最后统计 d p [ i ] dp[i] dp[i];如果 d p dp dp 数组中最大的数也比 s [ i ] s[i] s[i] 小,那么就将 s [ i ] s[i] s[i] 插入 d p dp dp 数组的最后一位数后面,让 d p dp dp 数组增加一个数。最后答案便是 d p dp dp 数组的长度。

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+5;

int n, b[N], to[N];
int dp[N], que[N], p;//dp[i]:长度为i的最长子串的最小结尾

int main(){
	// freopen("input.txt", "r", stdin);
	scanf("%d", &n);
	for(int i=1; i<=n; ++i){
		int a; scanf("%d", &a);
		to[a] = i;
	}
	for(int i=1; i<=n; ++i){
		scanf("%d", b+i);
		b[i] = to[b[i]];
	}
	memset(dp, 0x3f, sizeof dp);
	dp[0] = 0;
	for(int i=1; i<=n; ++i){
		if(dp[p]<b[i]){
			dp[++p] = b[i];
			continue;
		}
		int l = 1, r = p;
		while(l<r){
			int mid = (l+r)>>1;
			if(dp[mid]<b[i])
				l = mid+1;
			else
				r = mid;
		}
		dp[l] = min(dp[l], b[i]);
	}
	printf("%d", p);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值