The Longest Increasing Subsequence (LIS)
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)的更多相关文章
- [tem]Longest Increasing Subsequence(LIS)
Longest Increasing Subsequence(LIS) 一个美丽的名字 非常经典的线性结构dp [朴素]:O(n^2) d(i)=max{0,d(j) :j<i&& ...
- 300. Longest Increasing Subsequence(LIS最长递增子序列 动态规划)
Given an unsorted array of integers, find the length of longest increasing subsequence. For example, ...
- LeetCode -- Longest Increasing Subsequence(LIS)
Question: Given an unsorted array of integers, find the length of longest increasing subsequence. Fo ...
- [LintCode] Longest Increasing Subsequence 最长递增子序列
Given a sequence of integers, find the longest increasing subsequence (LIS). You code should return ...
- [Algorithms] Longest Increasing Subsequence
The Longest Increasing Subsequence (LIS) problem requires us to find a subsequence t of a given sequ ...
- 【Lintcode】076.Longest Increasing Subsequence
题目: Given a sequence of integers, find the longest increasing subsequence (LIS). You code should ret ...
- LintCode刷题笔记--Longest Increasing Subsequence
标签: 动态规划 描述: Given a sequence of integers, find the longest increasing subsequence (LIS). You code s ...
- 最长上升子序列 LIS(Longest Increasing Subsequence)
引出: 问题描述:给出一个序列a1,a2,a3,a4,a5,a6,a7….an,求它的一个子序列(设为s1,s2,…sn),使得这个子序列满足这样的性质,s1<s2<s3<…< ...
- [LeetCode] Longest Increasing Subsequence 最长递增子序列
Given an unsorted array of integers, find the length of longest increasing subsequence. For example, ...
随机推荐
- mysqli事务处理demo
<?php $mysqli=new mysqli("localhost", "root", "123456", "xsph ...
- Linq中查询List组合相同值数量大于1
List< select g.Key).ToList();
- jenkins配置记录(1)--添加用户权限
前一阵子在线上部署了一套jenkins环境,作为线上代码发布平台使用.部署记录:http://www.cnblogs.com/kevingrace/p/5651427.html 下面重点记录下jenk ...
- Xcode7 真机调试步骤以及遇到的问题解决办法
打开Xcode7,打开preference 添加自己的apple ID登陆上去 打开一个自己的想要运行在真机上的项目 插上自己的iPhone真机(真机没必要是最新的系统,没必要升级,我刚开始报错以为是 ...
- [vm]exsi的名词
早上想了想高可用.昨晚想学学虚拟vmware的一些东西. 物理机---exsi---vm 物理机-win7-vmware-vm 服务器高可用: 一 名词 1,虚拟网络 2,虚拟交换机 3,vm ...
- C# LUA 闭包
许多语言中有闭包的概念,C#的闭包以lambda表达式表现,可以实现与LUA完全一样的效果. //LUA------------------------------------------------ ...
- Spring如何处理线程并发
Spring如何处理线程并发 我们知道Spring通过各种DAO模板类降低了开发者使用各种数据持久技术的难度.这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突.我 ...
- linux实践——字符集
一.ASCII码 首先是看得懂ASCII码表: 二 八 十 十六 缩写/字符 0000 0000 0 0 00 NUL(null) 0000 0001 1 1 01 SOH(start of head ...
- 软件工程(QLGY2015)第二次作业点评(随机挑选20组点评)
相关博文目录: 第一次作业点评 第二次作业点评 第三次作业点评 说明:随机挑选20组点评,大家可以看看blog名字,github项目名字,看看那种是更好的,可以学习,每个小组都会反应出一些问题,希望能 ...
- Linux c实现服务端与客户端聊天
主要利用socket通信实现,具体代码如下 客户端: #include <stdio.h> #include <stdlib.h> #include <string.h& ...