NOJ1858 对于最长上升子序列O(nlogn)算法的深入理解

来源:互联网 发布:屏幕像素算法 编辑:程序博客网 时间:2024/06/05 15:58

最长上升子序列 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 1000000010using 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;}



0 0
原创粉丝点击