1. 程式人生 > 實用技巧 >P1439 【模板】最長公共子序列

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

最長公共子序列

Link

題目描述

給出 \(1,2,\ldots,n\) 的兩個排列 \(P_1\)\(P_2\) ,求它們的最長公共子序列。

輸入格式

第一行是一個數 \(n\)

接下來兩行,每行為 \(n\) 個數,為自然數 \(1,2,\ldots,n\) 的一個排列。

輸出格式

一個數,即最長公共子序列的長度。

輸入輸出樣例

輸入 #1

5 
3 2 1 4 5
1 2 3 4 5

輸出 #1

3

說明/提示

  • 對於 50% 的資料, \(n \le 10^3\)

  • 對於 100% 的資料, \(n \le 10^5\)

我做這道題的時候心路歷程是這樣的:

這不是個模板題嗎,水過去了 ---> wdnmd 這怎麼 \(TLE\)

了------>看了一眼資料範圍,我透,這題好狗啊。

先給出O(\(n^2\))的轉移方程吧:

\(f[i][j]\) 表示 第一個串的前 \(i\) 位,第二個串的前 \(j\) 位的最長公共子序列的長度。

那麼就有轉移:

  • \(a_i \neq b_j\) \(f[i][j] = max(f[i-1][j],f[i][j-1],f[i][j])\)
  • \(a_i == b_j\) \(f[i][j] = max(f[i-1][j],f[i][j-1],f[i][j])\)

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int a[100010],b[100010];
int f[1010][1010];
int main(){
	scanf("%d",&n);
	for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
	for(int j = 1; j <= n; j++) scanf("%d",&b[j]);
	f[0][1] = f[1][0] = 0;
	for(int i = 1; i <= n; i++)
        {
		for(int  j = 1; j <= n; j++)
               {
				f[i][j] = max(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);
				}
		}
	}
	printf("%d",f[n][n]);
	return 0;
} 

可這道題 \(O(n^2)\) 的資料顯然過不去。

我們這就需要仔細觀察一下這道題,然後你就會發現他有一個性質就是給出的兩個序列都是排列。

這意味著什麼?,也就是說 \(p_1\) 中的數一定·會在 \(p_2\) 中出現過。

所以,我們在 \(p_1\) 中排名靠前的數在 \(p_2\) 中排名也要靠前。

我們對 \(p_1\) 中得數求一下他在 \(p_2\) 出現的位置,然後求一個最長上升子序列就完事了。

Code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int n,ans,a[N],b[N],pos[N],tr[N];
int lowbit(int x){return x & -x;}
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s =s * 10+ch - '0'; ch = getchar();}
	return s * w;
}
void chenge(int x,int val)//樹狀陣列查詢字首最大值
{
	for(; x <= N-5; x += lowbit(x)) tr[x] = max(tr[x],val);
}
int ask(int x)
{
	int res = 0;
	for(; x; x -= lowbit(x)) res = max(res,tr[x]);
	return res;
}
int main()
{
	n = read();
	for(int i = 1; i <= n; i++) a[i] = read();
	for(int i = 1; i <= n; i++)
	{
		b[i] = read();
		pos[b[i]] = i;
	}
	for(int i = 1; i <= n; i++) a[i] = pos[a[i]];//記錄一下每個數在b中出現的位置
	for(int i = 1; i <= n; i++)
	{
		int tmp = ask(a[i]-1) + 1;
		chenge(a[i],tmp);
		ans = max(ans,tmp);
	}
	printf("%d\n",ans);
	return 0;
}