题型
给你一些 1 1 1到 n n n的排列,求最长公共子序列的长度。
铺垫(朴素的最长公共子序列)
两个序列分别是
a
i
(
1
≤
i
≤
n
)
a_i(1 \le i \le n)
ai(1≤i≤n)和
b
j
(
1
≤
j
≤
m
)
b_j(1 \le j \le m)
bj(1≤j≤m)。
设
d
p
i
,
j
dp_{i,j}
dpi,j表示第一个序列考虑到第
i
i
i位,第二个序列考虑到第
j
j
j位,最长公共子序列的长度。转移:
d
p
i
,
j
=
{
d
p
i
−
1
,
j
d
p
i
,
j
−
1
d
p
i
−
1
,
j
−
1
+
(
a
i
=
=
b
j
)
dp_{i,j} = \begin{cases} dp_{i-1,j} \\ dp_{i,j-1} \\ dp_{i-1,j-1}+(a_i==b_j) \end{cases}
dpi,j=⎩
⎨
⎧dpi−1,jdpi,j−1dpi−1,j−1+(ai==bj)
答案就是
d
p
n
,
m
dp_{n,m}
dpn,m。
例题1
题意
给出 1 , 2 , … , n 1,2,\ldots,n 1,2,…,n 的两个排列 P 1 P_1 P1 和 P 2 P_2 P2 ,求它们的最长公共子序列。 ( 1 ≤ n ≤ 1 0 5 ) (1 \le n \le 10^5) (1≤n≤105)
样例输入
5
3 2 1 4 5
1 2 3 4 5
样例输出
3
思路
发现朴素做法的数组会爆空间,此题特殊在它是
1
1
1到
n
n
n的排列,没有那么多数,既然两个数列的组成部分相同,只需要知道每个元素之间的相对位置关系,例如样例中第一个数列的
3
3
3在
1
1
1前面,而第二个数列的
1
1
1在
3
3
3前面,
1
1
1和
3
3
3肯定就不能同时出现在公共子序列里。
可以将第二个数列中每个数在第一个数列出现的位置再变成一个新的数组
p
s
ps
ps,样例中是3 2 1 4 5
,这个数组中的最长上升子序列的长度就是最长公共子序列的长度,也就代表着两个数列中相对位置完全相同的序列。(新数组的下标前后关系与第二个数列的前后关系对应,新数组的值与第一个数列的前后关系对应,当
i
<
j
i<j
i<j且
p
s
i
<
p
s
j
ps_i<ps_j
psi<psj,那么就符合两个数列的前后关系)
代码
#include<bits/stdc++.h>
using namespace std;
int n,p1[100005],p2[100005],ps[100005];
long long dp[100005],tr[400005],ans;
void update(int ps,int l,int r,int ups,long long v){
if(l==r){
tr[ps]+=v;
return;
}
int mid=(l+r)/2;
if(ups<=mid) update(ps*2,l,mid,ups,v);
else update(ps*2+1,mid+1,r,ups,v);
tr[ps]=max(tr[ps*2],tr[ps*2+1]);
}
long long query(int ps,int l,int r,int ql,int qr){
if(ql>r||qr<l) return 0;
else if(l>=ql&&r<=qr) return tr[ps];
int mid=(l+r)/2;
return max(query(ps*2,l,mid,ql,qr),query(ps*2+1,mid+1,r,ql,qr));
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>p1[i];
ps[p1[i]]=i;
}
for(int i=1;i<=n;i++){
cin>>p2[i];
p2[i]=ps[p2[i]];
}
dp[0]=0;
for(int i=1;i<=n;i++){
dp[i]=query(1,1,n+1,1,p2[i])+1;
update(1,1,n+1,p2[i]+1,dp[i]);
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
return 0;
}
例题2
题意
给你 k k k个 1 1 1到 n n n的排列,问这 k k k个数列的最长公共子序列的长度。
思路
这题感觉是上一题的升级版,然而上一题的思路不可行。但大体思想是一致的,上面提到两个数在两个数列中的相对位置关系如果相同,那么才可能出现在公共子序列中,所以可以处理出每两个数在
k
k
k个数列中分别是怎么样的先后顺序,如果两个数在
k
k
k个序列中的先后顺序都一致,那么就可以将两个数同时加入到公共子序列中。最长公共子序列就是由此产生的,比如:目前找到的关系有
1
1
1在
3
3
3前面,
5
5
5在
1
1
1前面,
3
3
3在
2
2
2前面,
4
4
4在
2
2
2前面,
6
6
6在
4
4
4前面,就可以建成一张图,用有向边表示数之间的关系。
这时最长路就是5 1 3 2
,这个序列满足上述所有找到的关系,且只包含了已经确定的前后关系,是一个符合要求的公共子序列。它同时也是最长的,所以就是最长公共子序列。
代码
#include<bits/stdc++.h>
using namespace std;
int n,k,a[10][1005],dp[1005],in[1005],ans;
bool e[10][1005][1005],ee[1005][1005];
//ee[i][j]代表两个数是否在k个数列中都是i在j前面
vector<int> go[1005];
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
ee[i][j]=1;
for(int i=1;i<=k;i++){
for(int j=1;j<=n;j++) cin>>a[i][j];
for(int j=1;j<=n;j++)
for(int k=j+1;k<=n;k++)
e[i][a[i][j]][a[i][k]]=1;
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
ee[j][k]&=e[i][j][k];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(ee[i][j])
go[i].push_back(j),in[j]++;
queue<int> q;
for(int i=1;i<=n;i++)
if(!in[i]) q.push(i),dp[i]=1;
while(!q.empty()){
int f=q.front();
q.pop();
int si=go[f].size();
for(int i=0;i<si;i++){
dp[go[f][i]]=max(dp[go[f][i]],dp[f]+1);
in[go[f][i]]--;
if(!in[go[f][i]]) q.push(go[f][i]);
}
}
for(int i=1;i<=n;i++) ans=max(ans,dp[i]);
cout<<ans<<endl;
return 0;
}
总结
当数列中的元素固定时,只需要考虑每两个数之间的先后关系,而不是具体的值。
附赠果子狸神图: