最长上升子序列 LIS 是一类非常经典的DP问题。在数组s中找出最长的上升子序列的长度。
很容易想出一个O(n^2)的算法,用dp[i]表示以i结尾的上升子序列的最大长度,由状态转移方程dp[i] = max(dp[j]+1) (j<i,s[j]<s[i]) 即可.
O(nlogn)用到了二分的方法,这要求dp数组是单调的。
定义dp[i] 表示长度为i的上升子序列的末尾最小值
(对dp数组的解释:
1.dp数组是不断更新的 随着向后遍历s dp的值一直在动态改变
2.dp数组应该初始化为INF
3.可以证明dp数组是严格单调递增的(除了最后的INF))
由前到后逐个考虑s中的元素,假设我们已经考虑完了前(i-1)个元素,开始考虑第i个元素.对于所有比s[i]小的dp[m]的值 应该都满足dp[m+1] = min(dp[m+1],s[i]);(*)
(解释:a[i]大于一个长度为m的上升子序列的末尾值,所以构成了一个新的长度为(m+1)的上升子序列。所以dp[m+1]为a[i]或者为之前得出的某个比a[i]小的值)
(*)式变形,可以得到若dp[m] < s[i] <= dp[m+1] 则用s[i]去更新dp[m+1]
又因为dp是严格递增的,所以对于一个s[i]只可能有一次更新的机会,用二分搜索可以知道这个位置就是lower_bound(dp+1,dp+n+1,s[i]).
直观来讲就是,对于dp数组不断地用读到的s[i]来更新它的第一个不小于s[i]的位置上的值。
参考题目:NOJ1858 智能飞弹(最长下降子序列)
#include <cstdio>
#include <algorithm>
#define INF 1000000010
using namespace std;
const int maxn = 1000010;
int s[maxn];
int dp[maxn];
int main()
{
int t;
scanf("%d",&t);
while(t--){
int n;
scanf("%d",&n);
for(int i = n ; i >= 1 ; i --) scanf("%d",&s[i]);//即从最长递减序列转化为最长递增序列
for(int i = 0 ; i <= n ; i ++) dp[i] = INF;
for(int i = 1 ; i <= n ; i ++){
dp[lower_bound(dp+1,dp+n+1,s[i])-dp] = s[i];
}
printf("%d\n",lower_bound(dp+1,dp+n+1,INF) - dp - 1);
}
return 0;
}