思想:贪心+二分
新建一个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;
}