P1439 【模板】最長公共子序列
DP的經典例題,適合學完導彈攔截後再來學習
O(\(N^2\))
按照DP常規思考方法
我們令dp[i][j]為P1序列前i個子序列和P2序列前j個子序列的最長公共子序列長度
- 注意,這是一種常見的設dp狀態的方式,可以積累)
所以我們進而思考狀態轉移方程
對於\(dp[i][j]\) 應該和\(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]\)有關
- 無論\(p1[i]\)是否等於\(p2[j],dp[i][j]=dp[i-1][j]\),同理,\(dp[i][j]=dp[i][j-1]\)
所以\(dp[i][j]=max(dp[i-1][j],dp[i][j-1])\) - 而如果\(p1[i]==p2[j]\) 那麼最前面的公共子序列長度即可以+1,所以\(dp[i][j]=dp[i-1][j-1]+1\)
- 而當我們狀態轉移,狀態從(i-1,j),(i-1,j-1),(i,j-1)這個三個轉移過來,這個又從(i-2,j),(i-2,j-2),(i-1,j-2),(i-2,j-1),(i-2,j-2),(i-1,j-1),(i-1,j-2),(i,j-2)轉移,以此類推,所以在狀態轉移到(i,j)之前,從(1,1)到(i-1,j)都有值,所以才有程式碼中的迴圈
綜上
$dp[i][j]=max(dp[i-1][j],dp[i][j-1]) $
\(if(p1[i]==p2[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1)\)
#include<iostream>
using namespace std;
const int maxn=1e4+10;
int n;
int a[maxn],b[maxn];
int f[maxn][maxn];
int main(){
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=n;++i) cin>>b[i];
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){//狀態轉移前都有值
f[i][j]=max(f[i-1][j],f[i][j-1]);
if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
cout<<f[n][n];
return 0;
}
O(\(nlogn\))
在第一種的處理方法中,我們忽略了題目條件中的一個性質:全排列
而此題與導彈攔截有類似之處,我們思考是否有可能將此題轉化為最長不下降子序列
在最長不下降子序列中,從一個序列中找一個最長不下降子序列
而此題中是找兩個最長公共子序列,所以我們找出的子序列即為另一個序列子序列,那就能解決該問題
(核心思路)
如果我們保證P1序列是上升序列,而從P2序列找到一個最長不下降子序列一定是P1序列的子序列,那麼該序列長度即為所求
重新編號就能保證P1序列是上升序列
- 舉例
P1:3 2 1 4 5
P2:1 2 3 4 5
重新編號(全排列保證了這樣重新編號的合理性)
P1: 1 2 3 4 5
P2: 3 2 1 4 5
- 重新編號也很簡單,有map對映即可
- 求最長上升子序列用導彈攔截的方法即可
CODE
#include<bits/stdc++.h>
using namespace std;
map<int,int>m;
int n;
const int maxn=1e5+10;
int a[maxn];
int f[maxn];
int main(){
cin>>n;
for(int i=1;i<=n;++i){
int x;
cin>>x;
m[x]=i;
}
int t=0;
for(int i=1;i<=n;++i){
int x,l=0,r=t;
cin>>x;
x=m[x];
while(l<=r){
int mid=(l+r)>>1;
if(x<f[mid]) r=mid-1;
else l=mid+1;
}
if(l>t) t=l;
f[l]=x;
}
cout<<t<<endl;
return 0;
}