一、背景
最长公共子序列
给定一个序列,问其中最长的递增子序列的长度。
子序列,说明可以不连续。
但此题数据过大,一般线性dp将会超时
二、线性DP
1.思路
时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)
先确定一个状态:
d
p
[
i
]
dp[i]
dp[i]表示以
a
[
i
]
a[i]
a[i]作为子序列结尾时最长子序列的长度。
那么有
d
p
[
j
]
=
m
a
x
{
d
p
[
i
]
+
1
}
a
[
j
]
>
a
[
i
]
,
0
≤
i
<
j
dp[j]=max\{ dp[i]+1\}_{a[j]>a[i],0\leq i<j}
dp[j]=max{dp[i]+1}a[j]>a[i],0≤i<j
这样遍历一遍过后可以得到所有位置的解,最后再遍历一遍就得到了满足题意的最优解。
2.代码
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int n;
cin>>n;
int a[n],b[n];
for(int i=0;i<n;i++)cin>>a[i],b[i]=1;
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if(a[i]>a[j])b[i]=max(b[i],b[j]+1);
}
}
for(int i=0;i<n;i++)b[0]=max(b[0],b[i]);
cout<<b[0];
}
三、贪心+二分
1.思路
时间复杂度为 O ( n l o g n ) O(n\ log\ n) O(n log n)
如果在处理完的数据中定义一个数组
l
o
w
[
]
low[\ ]
low[ ],
l
o
w
[
a
n
s
]
low[ans]
low[ans]表示长度为
a
n
s
ans
ans的子序列集合中,诸多结尾中最小的值,比如长度为
3
3
3的子序列有
{
{
1
、
2
、
3
}
,
{
4
、
5
、
6
}
,
{
7
、
8
、
9
}
}
\{\ \{1、2、3\},\{4、5、6\},\{7、8、9\}\}
{ {1、2、3},{4、5、6},{7、8、9}},那么此时
l
o
w
[
3
]
low[3]
low[3]的值为
3
3
3。
这里其实是采用了贪心的想法:保证此时长度为
a
n
s
ans
ans的结尾元素的值尽可能的小,那么就有利于后续接上,也就可以得出最长的上升子序列。
我们先以
a
n
s
ans
ans来表示已经处理过的数据的
L
I
S
LIS
LIS,此时下一个问题是如何对这个
l
o
w
low
low数组进行维护更新,当此时有一个数据
t
m
p
tmp
tmp:
1.当
t
m
p
>
l
o
w
[
a
n
s
]
tmp>low[ans]
tmp>low[ans],也就是
t
m
p
tmp
tmp可以接在此时最长的子序列后面,那么此时的
a
n
s
ans
ans就需要进行更新
⟹
l
o
w
[
+
+
a
n
s
]
=
t
m
p
\Longrightarrow low[++ans]=tmp
⟹low[++ans]=tmp
2.当
t
m
p
<
l
o
w
[
a
n
s
]
tmp <low[ans]
tmp<low[ans],即无法用来直接更新
L
I
S
LIS
LIS,那么我们要如何处理这个
t
m
p
tmp
tmp呢?
不妨以一个例子来说明,给定一个序列
{
1
,
2
,
6
,
8
,
4
}
\{1,2,6,8,4\}
{1,2,6,8,4}
处理完前四个数据后,有表如下:
l o w [ 1 ] low[1] low[1] | l o w [ 2 ] low[2] low[2] | l o w [ 3 ] low[3] low[3] | l o w [ 4 ] low[4] low[4] |
---|---|---|---|
1 | 2 | 6 | 8 |
首先,
4
4
4当然可以作为长度为
1
1
1的子序列的最后一个元素,即自己本身,但此时
l
o
w
[
1
]
low[1]
low[1]为
1
1
1,这不满足我们的贪心策略;
再看
l
o
w
[
2
]
low[2]
low[2]为
2
2
2,
l
o
w
[
2
]
<
4
low[2]<4
low[2]<4仍然不可用来更新,但此时
l
o
w
[
3
]
low[3]
low[3]为
6
6
6,
l
o
w
[
3
]
>
4
low[3]>4
low[3]>4,这表明4完全可以在原来长度为
2
2
2的子序列后面续接,同时得到一个长度为
3
3
3且结尾元素小于原来的
l
o
w
[
3
]
low[3]
low[3]的子序列,那么此时就需要更新
l
o
w
[
3
]
low[3]
low[3]的值了:
l o w [ 1 ] low[1] low[1] | l o w [ 2 ] low[2] low[2] | l o w [ 3 ] low[3] low[3] | l o w [ 4 ] low[4] low[4] |
---|---|---|---|
1 | 2 | 4 | 8 |
此时我们再观察这个
l
o
w
[
3
]
low[3]
low[3]在
l
o
w
low
low数组中与
4
4
4的关系,可以发现
l
o
w
[
3
]
low[3]
low[3]是
l
o
w
low
low数组中第一个大于
4
4
4的值
但是如果此时继续加入一个值为
4
4
4的数据,又要如何处理呢?
此时要求的是最长上升子序列,如果把此时的
l
o
w
[
4
]
low[4]
low[4]更新为4,所得的是最长非降序子序列,所以为了格式的统一,不妨将上述的更新改为更新第一个大于等于
t
m
p
tmp
tmp的值,同时可以使用
lower_bound(beg,end,val)
//返回一个迭代器,指向非递减序列[first, last)中的第一个大于等于(>=)val的位置**
来简化代码。
2.代码
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int a[N];
int low[N];
int n;
int ans;
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
memset(low,0x3f,sizeof low);
ans=1;
low[ans]=a[1];
for(int i=2;i<=n;i++){
if(a[i]>low[ans]){
low[++ans]=a[i];
}
else{
//int tmp=lower_bound(low+1,low+ans, a[i])-low;//返回第一个大于等于a[i]的位置,将该处的值进行更新
low[lower_bound(low+1,low+ans, a[i])-low]=a[i];
}
}
cout<<ans;
return 0;
}