最长公共子序列

DP的经典例题,适合学完导弹拦截后再来学习

\(\\\)

P1439 【模板】最长公共子序列

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
  1. 重新编号也很简单,有map映射即可
  2. 求最长上升子序列用导弹拦截的方法即可

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; 
}

posted @ 2024-11-03 13:04  归游  阅读(16)  评论(0编辑  收藏  举报