前言
Q:标题是什么意思?
A:
LIS
L
I
S
指最长上升子序列,
LCS
L
C
S
指最长公共子序列,
LCIS
L
C
I
S
指最长上升公共子序列。
不清楚上面几个定义的建议复习一下“子序列”之类的概念。这里的上升都是严格的。
这几种经常会在OI竞赛中遇到,属于基本功吧。搞个总结。
LIS
例1
给定一个长度为
n
n
的数列 ,求其
LIS
L
I
S
的长度。
n≤1000
n
≤
1000
。
解
我们拿个样例,比如:
1 3 6 4 9 7 8
可以看出它的
LIS
L
I
S
是 1 3 4 7 8。
分析可以使用化归法,把
n
n
的问题化成 的问题。(类似动规思想)
如果我们知道前面
n−1
n
−
1
个数的
LIS
L
I
S
,怎么求第
n
n
的?
有人可能会想:如果 能进就放进去不就好了?
但是前面的
n−1
n
−
1
个数可能有多个
LIS
L
I
S
,那要塞哪个呢?
很容易就会想到,肯定是尽量使得
LIS
L
I
S
最后那个数最小的那个啊。
所以我们这样想下去,就是要保证
LIS
L
I
S
中每一位数都是尽可能小的。
拿样例举例,如果当前做到第5位,目前满足上述条件的
LIS
L
I
S
为:
1 3 4 9
加入7,发现7没9大,加不进去。
但是为了使每一位最小,要把9换掉,就变成
1 3 4 7
OK。这样
LIS
L
I
S
的做法已经出来了。
当新加入一个数时,在LIS里从找一个最大的比它小的数,然后与其后面一个数更换。如果这个数在
LIS
L
I
S
尾,那么直接插入。这样是
O(n2)
O
(
n
2
)
的。
例2
同例1。数据范围加大。 n≤50000 n ≤ 50000 。
解
分析上面解答中的步骤,我们发现“找一个最大的比它小的数”可以二分(由于
LIS
L
I
S
单调),复杂度就优化为
O(nlogn)
O
(
n
l
o
g
n
)
了。
代码:
inline int LIS()
{
lis[len = 1] = a[1];
for (R int i=2;i<=n;++i)
{
if(a[i] > lis[len]) lis[++len]=a[i];
else
{
R int pos=lower_bound(lis+1,lis+len+1,a[i])-lis;
lis[pos] = a[i];
}
}
return len;
}
LCS
例3
给定长度分别为
n,m
n
,
m
的两个数列
a,b
a
,
b
,求它们的
LCS
L
C
S
。
n,m≤1000
n
,
m
≤
1000
。
解
仍然考虑
dp
d
p
思想。记
fi,j
f
i
,
j
为数列
a
a
做到 ,数列
b
b
做到 的
LCS
L
C
S
的长度,考虑转移。
如果
ai=bj
a
i
=
b
j
,那
fi,j=fi−1,j−1+1
f
i
,
j
=
f
i
−
1
,
j
−
1
+
1
;
否则,
fi,j=max(fi−1,j,fi,j−1)
f
i
,
j
=
m
a
x
(
f
i
−
1
,
j
,
f
i
,
j
−
1
)
。
例4
同例3,数据范围扩大。 n,m≤50000 n , m ≤ 50000 ,保证相同元素在一个数列中出现的次数不超过20次。
解
给个样例:
1 2 3 4 5 6
3 6 2 7 3 6
6 5 2 7 7 3
从上到下分别为序号、数列
a
a
、数列 。通过观察可以发现
LCS
L
C
S
为 6 2 7 3
由于范围过大显然不能用dp的做法了,这里介绍一种
O(20nlog(20n))
O
(
20
n
l
o
g
(
20
n
)
)
的做法(20的含义见题面)
我们把
a
a
中每个元素在 中出现的位置记下来从大到小,然后和原来在
a
a
数列中的数替换位置。比如第二行处理后就变成:
(6) (1) (3) (5 4) (6) (1)
然后对它求一个 ,再替换为对应的数即为 LCS L C S 。
1 3 5 6
替换后
6 2 7 3
原理:最长公共子序列中的序列序号都是递增的。
代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define R register
#define Maxn 1000005
#define Maxnum 1000005
int n,m,s1[Maxn],s2,a[Maxn],lena,lis[Maxn],len;
int app[Maxnum][22],t[Maxnum];
inline int LIS()
{
lis[len = 1] = a[1];
for (R int i=2;i<=lena;++i)
{
if(a[i] > lis[len]) lis[++len]=a[i];
else
{
R int pos=lower_bound(lis+1,lis+len+1,a[i])-lis;
lis[pos] = a[i];
}
}
return len;
}
int main()
{
freopen("input9.txt","r",stdin);
scanf("%d",&n);
for (R int i=1;i<=n;++i) scanf("%d",&s1[i]);
scanf("%d",&m);
for (R int i=1;i<=m;++i)
{
scanf("%d",&s2);
app[s2][++t[s2]] = i;
}
for (R int i=1;i<=n;++i)
{
sort(app[s1[i]]+1,app[s1[i]]+t[s1[i]]+1);
for (R int j=t[s1[i]];j;j--) a[++lena]=app[s1[i]][j];
}
printf("%d\n",LIS());
return 0;
}
LCIS
例5
给定长度分别为
n,m
n
,
m
的两个数列
a,b
a
,
b
,求它们的
LCIS
L
C
I
S
。
n,m≤1000
n
,
m
≤
1000
。
解
貌似也有人把
LCIS
L
C
I
S
叫
LICS
L
I
C
S
来着 …、
仍然dp,类比
LCS
L
C
S
记
fi,j
f
i
,
j
为
a
a
做到 ,
b
b
做到 ,并且以
bj
b
j
结尾的
LCIS
L
C
I
S
的长度。
如果
ai=bj
a
i
=
b
j
,那么我们要判断一下是否上升,
于是我们就往回找,找到最长的可以接入的来更新,即:
fi,j=max(fi,k),k∈[1,j−1]+1
f
i
,
j
=
m
a
x
(
f
i
,
k
)
,
k
∈
[
1
,
j
−
1
]
+
1
否则,就直接从之前转移
fi,j=fi−1,j
f
i
,
j
=
f
i
−
1
,
j