LIS(最长上升子序列)nlogn求法

思想:贪心+二分

新建一个low数组,low[i]表示长度为i的LIS结尾元素的最小值。对于一个上升子序列,显然其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长。因此,我们只需要维护low数组,对于每一个a[i],如果a[i] > low[当前最长的LIS长度],就把a[i]接到当前最长的LIS后面,即low[++当前最长的LIS长度]=a[i]。 
那么,怎么维护low数组呢? 
对于每一个a[i],如果a[i]能接到LIS后面,就接上去;否则,就用a[i]取更新low数组。具体方法是,在low数组中找到第一个大于等于a[i]的元素low[j],用a[i]去更新low[j]。如果从头到尾扫一遍low数组的话,时间复杂度仍是O(n^2)。我们注意到low数组内部一定是单调不降的,所有我们可以二分low数组,找出第一个大于等于a[i]的元素。二分一次low数组的时间复杂度的O(lgn),所以总的时间复杂度是O(nlogn)

下面上LIS模板:

#include<cstdio>
#include <iostream>
#include<cstring>
#include<algorithm>
#define MAXN 100005
using namespace std;
int arr[MAXN],ans[MAXN],len; 
int main(){
	int p = 0;
	while(~scanf("%d",&arr[++p]));
	p--;
	memset(ans,0,sizeof ans);
	ans[1] = arr[1];
	len=1;
	for(int i=2; i<=p; ++i){
		if(arr[i] > ans[len])
			ans[++len]=arr[i];//如果大于末尾的数, 直接放在末尾,长度+1 
		else{
			int pos=lower_bound(ans+1,ans+len+1,arr[i])-ans; //如果比末尾的数小,则找到数组中第一个比arr[i]大或相等的位置,并将该位置的值覆盖为arr[i] 
			ans[pos] = arr[i];
			}
	}
	printf("%d\n",len);
	return 0;
}

这里有lower_bound()和upper_bound()的用法:lower_bound VS upper_bound(用法详解)

下面看一道例题,更深的理解下LIS思想

导弹拦截

题意很明确,求出所给序列的最大不上升子序列和最大上升子序列,

什么是最大不上升子序列呢?

举个栗子:90 103 99  83 102 70 86 70 99 71

此序列的LIS是:83 86 99

而此序列的最大不上升子序列是:103 99 83 70 70

也就是说子序列中元素可以重复,首先我们可以求LIS,而求下降的子序列就可以将所给序列倒转过来,然后求一下LIS,

可是这里需要注意,我们不仅要求下降的,如果两个元素相同也要算上,那么我们就要将LIS的代码稍作一下改动,

首先看下序列倒过来后:71 99 70 86 70 102 83 99 103 90

	for(int i=2; i<=p; ++i){
		if(arr[i] >= ans[len])
			ans[++len]=arr[i];//如果大于等于末尾的数, 直接放在末尾,长度+1 
		else{
			int pos = upper_bound(ans+1,ans+len+1,arr[i])-ans; //如果比末尾的数小,则找到数组中第一个比arr[i]大的位置,并将该位置的值改变为arr[i] 
			ans[pos] = arr[i];
			}
	}

这里要区分下upper_bound,还是看上面那个栗子,如果我们找到第二个70的时候,进入else,如果按照low_bound()的原理,会直接找到第一个大于或等于70的数,看下ans的目前状态:ans[] = 70,86;显然70会找到70并且覆盖掉它,这是我们不希望看到的,这会导致最后的结果长度少一,所以我们这里要用upper_bound找到第一个大于70的数,让等于的情况合法,这样70覆盖86,ans[] = 70,70; 这样一来我们就得到了最长不下降子序列的长度,是不是更深入地理解LIS的过程了呢?

下面上本题代码:

#include<cstdio>
#include <iostream>
#include<cstring>
#include<algorithm>
#define MAXN 100005
using namespace std;
int arr[MAXN],ans[MAXN],len; 
int main(){
    int p = 0;
    while(~scanf("%d",&arr[++p]));
    p--;
    for(int i = 1; i <= (p >> 1); i++) {
        swap(arr[i], arr[p - i + 1]);
    }
    memset(ans,0,sizeof ans);
    ans[1] = arr[1];
    len=1;
    for(int i=2; i<=p; ++i){
        if(arr[i] >= ans[len])
            ans[++len]=arr[i];//如果大于等于末尾的数, 直接放在末尾,长度+1 
        else{
            int pos = upper_bound(ans+1,ans+len+1,arr[i])-ans; //如果比末尾的数小,则找到数组中第一个比arr[i]大的位置,并将该位置的值改变为arr[i] 
            ans[pos] = arr[i];
            }
    }
    for(int i = 1; i <= (p >> 1); i++) {
        swap(arr[i], arr[p - i + 1]);
    }
    printf("%d\n",len);
    memset(ans,0,sizeof ans);
    ans[1] = arr[1];
    len=1;
    for(int i=2; i<=p; ++i){
        if(arr[i] > ans[len])
            ans[++len]=arr[i];//如果大于末尾的数, 直接放在末尾,长度+1 
        else{
            int pos=lower_bound(ans+1,ans+len+1,arr[i])-ans; //如果比末尾的数小,则找到数组中第一个比arr[i]大或等于的位置,并将该位置的值改变为arr[i] 
            ans[pos] = arr[i];
            }
    }
    printf("%d\n",len);
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值