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, ...
随机推荐
- jsp 微信公众平台 token验证(php、jsp)(转载)
微信公众平台现在推出自动回复消息接口,但是由于是接口内容用的是PHP语言写的,很多地方操作起来让本人这个对java比较熟悉的小伙很别扭,所以仿照PHP的接口代码做了一套jsp语言编写的接口. 首先先把 ...
- View (一)LayoutInflater()方法详解
相信接 触Android久一点的朋友对于LayoutInflater一定不会陌生,都会知道它主要是用于加载布局的.而刚接触Android的朋友可能对 LayoutInflater不怎么熟悉,因为加载布 ...
- 用于json的 .ashx 小细节
public void ProcessRequest(HttpContext context) { switch (action) { case "attribute_field_valid ...
- Solaris 和linux 之oracle 数据库的安装
本篇博文前面是一些基础知识介绍,后面才是总结篇. 一.在solaris上面装oracle 10g教程 目前官网已经没有32位的oracle11g了,取而代之的都是64位的oracle11g,为了能在3 ...
- 访问 IIS 元数据库失败 的解决方法
系统是windows xp sp3,vs2010.安装了iis 5.1 ,创建了虚拟目录之后copy进去一个web sercices测试下能不能正常工作. 出现如下问题: 访问 IIS 元数据库失败. ...
- Linux及安全——Linux基础实践
Linux及安全——Linux基础实践 一.实践一:掌握软件源的维护方法,配置系统使用教育网内的软件源镜像.掌握通过软件源来查找,安装,卸载,更新软件的方法. 1.软件源的维护方法 Ubuntu的软件 ...
- [NOIP2016-day1-T2]天天爱跑步running_题解
题目来源:http://www.lydsy.com/JudgeOnline/problem.php?id=4719 镇楼图: noip滚粗后..订正的第一题. 题目大意: 有若干条路径在一棵树上,问每 ...
- iOS中偏好设置的创建,数据写入与读取
NSUserDefaults与NSDictinary? 应用通过NSUserDefaults用键值对的方式来读取和保存偏好设置数据,与通过键从NSDictionary对象中获取数据一样,不同之处在于N ...
- 解决装系统选中的磁盘采用的是GPT分区形式
今天给服务器重装系统碰到的问题,记录一下 当时是按正常的操作:到了装系统选盘的时候是找不到盘符的,加载了raid驱动,然后顺利找到盘符,然后格式化了以前的C盘, 结果无法选中格式化后的C盘,无法下一步 ...
- 第一个C语言编译器是怎样编写的?
首先向C语言之父Dennis MacAlistair Ritchie致敬! 当今几乎所有的实用的编译器/解释器(以下统称编译器)都是用C语言编写的,有一些语言比如Clojure,Jython等是基于J ...