思路:
1.该题题意是求不严格递减的子序列(即序列中相邻元素可以相等)个数最少有多少个;
2.有一个结论:该序列中最长递增子序列(LIS)的长度即为不严格递减子序列最少的个数;
严格证明(有一部分不会证orz):
(1)首先证明LIS中任意两个元素都来自不同的递减序列:
如果存在a
和b
,在原序列中,a
的位置在b
之前,如果它们在LIS中,则满足a<b
,如果在同一递减序列中则满足a>=b
,矛盾,所以得证;
(2)再证明n
个递减序列中,每个序列抽出一个数,则它们可以成为一条LIS;(这个不太会证orz)
换一个思路也许可以理解这个结论:
我们从开始往后遍历原序列,同时维护一个最长递增子序列LIS,初始就是第一个数,每往后遍历一个数,在LIS里寻找一个最小的大于等于该数的数,将这个数加到其后面,同时更新该数为当前遍历到的数,如果找不到,则将其加入LIS,那最终LIS长度就是(没有找到的次数+1),自然就是递减序列最少的个数了~;
例如:100 90 80 90 100 70 60 81 82 83
遍历完前三个数后,LIS里只有一个80
,遍历到第四个数90
时,在LIS里寻找一个最小的大于等于90
的数,可以让90
加到后面,发现没有,则就将90
加入LIS,100
也是这个道理,此时LIS里是[80,90,100]
,遍历完整个序列后,LIS里就是[60,81,82,83]
,这个序列出现了3
次不能加到前者后面的情况(即需要重新开一个递减序列)==(需要加入值到LIS里)
,那递减序列最少的个数就等于LIS序列的长度了~
3.下面的任务就是求LIS的长度了~
定义序列数组为a[i]
有两种思路
(1)定义dp[i]
为以a[i]
为结尾的LIS长度,得递推关系
该方法复杂度为
(2)定义dp[i]
为长度为i
的LIS中末尾元素的最小值,想法就是同一长度的子序列,末尾元素更小的序列更具有优势。首先更新所有dp[i]=INF
,递推关系就是,从前往后遍历原序列,对于每一个j
,如果i==0
或a[j]>dp[i-1]
,则
最终找到的最大的i
使得dp[i]!=INF
的(i+1)
即为LIS长度;
该方法复杂度仅有
代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX_N=1e4;
const int INF=1e9;
int n;
int a[MAX_N];
int dp[MAX_N]; //长度为i+1的递增子序列末尾元素的最小值
void solve(){
fill(dp,dp+n,INF);
for(int i=0;i<n;i++){
*lower_bound(dp,dp+n,a[i])=a[i];
}
printf("%d
",lower_bound(dp,dp+n,INF)-dp);
}
void clear(){
for(int i=0;i<=n;i++) a[i]=dp[i]=0;
}
int main(){
while(~scanf("%d",&n)){
for(int i=0;i<n;i++) scanf("%d",a+i);
solve();
clear();
}
return 0;
}