传送门

The task is to find the length of the longest subsequence in a given array of integers such that all elements of the subsequence are sorted in ascending order. For example, the length of the LIS for { 15, 27, 14, 38, 26, 55, 46, 65, 85 } is 6 and the longest increasing subsequence is {15, 27, 38, 55, 65, 85}.

In this challenge you simply have to find the length of the longest strictly increasing sub-sequence of the given sequence.

Input Format

In the first line of input, there is a single number N. In the next N lines input the value of a[i].

Constraints

1 ≤ N ≤ 106

1 ≤ a[i] ≤ 105

Output Format

In a single line, output the length of the longest increasing sub-sequence.

Sample Input

5
2
7
4
3
8

Sample Output

3

Explanation

{2,7,8} is the longest increasing sub-sequence, hence the answer is 3 (the length of this sub-sequence).


十分重要的基础DP问题,是学习用各种方式优化DP的一个很好的例子。

设DP[i]表示以a[i]结尾的LIS的长度,则状态转移方程为

DP[1]=1

DP[i] = max{DP[j], j<i && a[j]<a[i]} + 1, i>1

暴力求解复杂度为O(N^2)。

优化1

考虑函数f(L):长度为 L 的 Increasing Sequence (IS) 的最小结尾, 显然f(L)是单调递增的。

从左到右扫描数组,维护动态的f(L)。

再看上面的DP方程,考虑我们维护的这个函数f(L)如何加速DP状态转移时所需的查询。

显然我们以a[i]为key,二分查询f(L),得到max{L, f(L)<a[i]}

复杂度降为O(N*logN)

这一类DP优化方法可归纳为 加速DP Query

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX_N=1e6+, oo=1e6;
int A[MAX_N], f[MAX_N];
//二分法: binary_search(y, f())
//设有一个单调(不要求严格单调)的函数f()
//可以在O(log N)的时间内求解以下问题:
//给定y,求满足f(x)<=y/f(x)>=y的最大/最小的x
// int binary_search(int y, int l, int r){ //y is the key
int mid;
while(r-l>){ //r is illegal
mid=(l+r)>>;
if(f[mid]<y){
l=mid;
}
else r=mid;
}
return l;
} int main(){
//freopen("in", "r", stdin);
int N, ans=;
scanf("%d", &N);
for(int i=; i<=N; i++) scanf("%d", A+i); f[]=;
for(int i=; i<=N; i++) f[i]=oo;
for(int i=; i<=N; i++){
int tmp=binary_search(A[i], , ans+)+;
ans=max(ans, tmp);
f[tmp]=min(f[tmp], A[i]);
}
printf("%d\n", ans);
return ;
}

优化2

具体地说,可把第一种优化方式称作单调性优化。我们再考虑一种优化方式。

仍观察DP方程 DP[i]=max{DP[j], j<i && a[j]<a[j]} + 1

我们考虑用数据结构加速查询,查询的键值 (key) 是a[i], ( 第一个条件 i<j 是自然满足的), 对应的value就是max{dp[j], a[j]<=a[i]}。我们把用于查询的 <key, value> 维护成线段树。其中key就是节点的L, R, 这样<key, value> 查询就是RMQ(Range Maximum Query)。

这样可以优化到O(NlogM),M是a[i]的最大值,可通过NlogN的离散化优化到NlogN (M比N大很多时才有必要离散化)。

#include<bits/stdc++.h>
using namespace std;
const int MAX_M=1e5+, MAX_N=1e6+;
int ma[MAX_M<<];
void insert(int id, int L, int R, int pos, int v){
if(L==R){
ma[id]=v;
}
else{
int mid=(L+R)>>;
if(pos<=mid){
insert(id<<, L, mid, pos, v);
}
else{
insert(id<<|, mid+, R, pos, v);
}
ma[id]=max(ma[id<<], ma[id<<|]);
}
}
int query(int id, int L, int R, int l, int r){
if(l<=L && R<=r) return ma[id];
int mid=(L+R)>>;
int res=;
if(l<=mid){
res=max(res, query(id<<, L, mid, l, r));
}
if(r>mid){
res=max(res, query(id<<|, mid+, R, l, r));
}
return res;
}
int a[MAX_N];
int main(){
//freopen("in", "r", stdin);
int N;
//single case, leave out initialization
scanf("%d", &N);
int rb;
for(int i=; i<N; i++) scanf("%d", a+i), rb=max(rb, a[i]);
int ans=;
for(int i=; i<N; i++){
int tmp=query(, , rb, , a[i]-)+;
ans=max(ans, tmp);
insert(, , rb, a[i], tmp); //over-head
}
printf("%d\n", ans);
return ;
}

这种优化方法具体地可以称作数据结构优化
这种方法的弊端:

代码量大、常数大

另外尤其注意插入操作

insert(, , rb, a[i], tmp); 

这个操作往往并没有增大 (0, a[i]) 区间的最大值,但是这里每次更新都要深入到叶子节点,不能不说是一种冗余 (树状数组就很好地避免了这种冗余)。

其实没必要用线段树来维护查询,注意到每次查询的区间左端都是0,可用树状数组(Binary Indexed Tree, BIT)代替。

具体而言,BIT的每个节点维护对应区间的最大值,更新与查询的复杂度都是O(logM)总体复杂度为O(NlogM)。

#include<bits/stdc++.h>
using namespace std;
const int MAX_N=1e6+, MAX_M=1e5+;
int bit[MAX_M], M;
int a[MAX_N];
int query(int i){
int res=;
while(i){
res=max(res, bit[i]);
i-=i&-i;
}
return res;
}
void modify(int i, int v){
while(i<=M){
if(bit[i]>=v) return;
bit[i]=v;
i+=i&-i;
}
}
int main(){
//freopen("in", "r", stdin);
int N;
scanf("%d", &N);
M=;
for(int i=; i<N; i++) scanf("%d", a+i), M=max(M, a[i]);
int ans=;
int tmp;
for(int i=; i<N; i++){
tmp=query(a[i]-)+;
modify(a[i], tmp);
ans=max(ans, tmp);
}
printf("%d\n", ans);
return ;
}

BIT代码短、速度快,可与二分查询媲美。

P.S. 写这篇随笔时,专门考虑了如何用BIT维护prefix maximum。最初的想法是将prefix maximum维护(表示)成prefix sum,发现行不通。结论是自己学得太死,全不知变通。其实BIT和线段树一样,两者的节点表示的都是区间,可以说没有本质差别。而我只知道BIT可维护prefix sum, 但对于BIT是如何维护prefix sum的却不了然,所以不能灵活应用。

Given a table of elements, it is sometimes desirable to calculate the running total of values up to each index according to some associative binary operation (addition on integers, for example). Fenwick trees provide a method to query the running total at any index, in addition to allowing changes to the underlying value table and having all further queries reflect those changes.

The Longest Increasing Subsequence (LIS)的更多相关文章

  1. [tem]Longest Increasing Subsequence(LIS)

    Longest Increasing Subsequence(LIS) 一个美丽的名字 非常经典的线性结构dp [朴素]:O(n^2) d(i)=max{0,d(j) :j<i&& ...

  2. 300. Longest Increasing Subsequence(LIS最长递增子序列 动态规划)

    Given an unsorted array of integers, find the length of longest increasing subsequence. For example, ...

  3. LeetCode -- Longest Increasing Subsequence(LIS)

    Question: Given an unsorted array of integers, find the length of longest increasing subsequence. Fo ...

  4. [LintCode] Longest Increasing Subsequence 最长递增子序列

    Given a sequence of integers, find the longest increasing subsequence (LIS). You code should return ...

  5. [Algorithms] Longest Increasing Subsequence

    The Longest Increasing Subsequence (LIS) problem requires us to find a subsequence t of a given sequ ...

  6. 【Lintcode】076.Longest Increasing Subsequence

    题目: Given a sequence of integers, find the longest increasing subsequence (LIS). You code should ret ...

  7. LintCode刷题笔记--Longest Increasing Subsequence

    标签: 动态规划 描述: Given a sequence of integers, find the longest increasing subsequence (LIS). You code s ...

  8. 最长上升子序列 LIS(Longest Increasing Subsequence)

    引出: 问题描述:给出一个序列a1,a2,a3,a4,a5,a6,a7….an,求它的一个子序列(设为s1,s2,…sn),使得这个子序列满足这样的性质,s1<s2<s3<…< ...

  9. [LeetCode] Longest Increasing Subsequence 最长递增子序列

    Given an unsorted array of integers, find the length of longest increasing subsequence. For example, ...

随机推荐

  1. [Elixir007] on_definition规范函数定义时的各种潜规则

    1.需求 写一个基于memcache的cache模块, 需要在key前面加上特定的前缀, 所以user cache的原始的store函数应该写成 # user.exdef store(user_id, ...

  2. webpack+react+redux+es6

    一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...

  3. JNI 程序开发

    参考资料: http://blog.csdn.net/wwj_748/article/details/28136061 JNI_最简单的Java调用C/C++代码 http://blog.csdn.n ...

  4. PHP基础01:环境搭建

    1.只会前端的只是有时候让我感到很苦恼,所以决定从今天开始学习后端,看了一些关于后端语言的比较帖子,决定选择php作为我的第一门后端语言.这个是我自己的学习笔记.方便自己复习,不写下来会太无聊了. 第 ...

  5. 解决 Windows Update 时提示当前无法检查更新,因为未运行服务

    故障:打开“Windows Update”出现红色盾牌图标 点击“检查更新”,出现“Windows Update 当前无法检查更新,因为未运行服务.您可能需要重新启动计算机” 查看“Windows U ...

  6. pandas 给数据打标签

    import numpy as np import pandas as pd df = pd.DataFrame(np.random.randint(0,100,100), columns=['sco ...

  7. CSS 实现加载动画之六-大风车

    这个动画改编自光盘旋转,前几个步骤一样,最后一步不同.光盘旋转的最后一步是外层容器加个圆形的白色边框,多余部分隐藏,这个案例则是在每个旋转的子元素的顶部或底部增加一个三角形的元素,构成风车旋转的角. ...

  8. java.util.ConcurrentModificationException 解决办法

    在使用iterator.hasNext()操作迭代器的时候,如果此时迭代的对象发生改变,比如插入了新数据,或者有数据被删除. 则使用会报以下异常:Java.util.ConcurrentModific ...

  9. [CareerCup] 7.1 Basketball Shooting Game 投篮游戏

    7.1 You have a basketball hoop and someone says that you can play one of two games. Game 1: You get ...

  10. iOS中几种定时器

    在软件开发过程中,我们常常需要在某个时间后执行某个方法,或者是按照某个周期一直执行某个方法.在这个时候,我们就需要用到定时器. iOS中定时器NSTimer的使用   1.初始化 + (NSTimer ...