洛谷 P1439 最长公共子序列(LCS转LIS)

题目链接:点击这里
洛谷 P1439 最长公共子序列(LCS转LIS)_第1张图片
无法相信这是模板题QAQ

暴力递归能更好地帮助我们从宏观上理解题目,TLE:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

using namespace std;
typedef long long ll;
const int MOD = 10000007;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1.0);
const int maxn = 100010;

int n;
int A[maxn], B[maxn];

int search(int i, int j)
{
	if(i>n||j>n)	return -1;
	if(i==n&&j==n)	return 0;
	
	if(A[i]==B[j])
		return search(i+1, j+1) + 1;	//Ai取,Bj取
	else
		return max(search(i, j+1), search(i+1, j));
		//Ai不取,Bj取
		//Ai取,Bj不取
		//search(i, j):Ai不取、Bj不取,跳过
}

int main()
{
	scanf("%d", &n);
	for(int i = 0; i < n; ++i)
		scanf("%d", &A[i]);
	for(int i = 0; i < n; ++i)
		scanf("%d", &B[i]);
		
	printf("%d\n", search(0,0));
	
	return 0;
}

下面来看动态规划的做法,时间复杂度 O ( n 2 ) O(n^2) O(n2)

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 A A A i i i 号位 和 B B B j j j 号位之前的 LCS 长度 (下标从 1 1 1 开始)

那么可以根据 A [ i ] A[i] A[i] B [ j ] B[j] B[j] 的情况,分为两种决策:

  1. A [ i ] = = B [ j ] A[i] == B[j] A[i]==B[j],则 A A A B B B 的LCS增加了 1 1 1 位,即有 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i-1][j-1] + 1 dp[i][j]=dp[i1][j1]+1

  2. A [ i ]   ! = B [ j ] A[i] \ != B[j] A[i] !=B[j],则 A A A i i i 号位和 B B B j j j 号位之前的LCS无法延长,因此 d p [ i ] [ j ] dp[i][j] dp[i][j] 将会继承 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j] d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1] 中的较大值,即有 d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j ] ,   d p [ i ] [ j − 1 ] } dp[i][j] = max\left\{dp[i-1][j],\ dp[i][j-1]\right\} dp[i][j]=max{dp[i1][j], dp[i][j1]}

由此可以得到状态转移方程:

边界: d p [ i ] [ 0 ] = d p [ 0 ] [ j ] = 0 dp[i][0] = dp[0][j] = 0 dp[i][0]=dp[0][j]=0

如果用常规方法求解,不仅会爆数组,还会超时,显然只能拿50分:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

using namespace std;
typedef long long ll;
const int MOD = 10000007;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1.0);
const int maxn = 100010;

int n;
int A[maxn], B[maxn];
int dp[1010][1010];

int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i)
		scanf("%d", &A[i]);
	for(int i = 1; i <= n; ++i)
		scanf("%d", &B[i]);
	
	//边界
	for(int i = 0; i <= n; i++)
		dp[i][0]= 0;
	
	for(int j = 0; j <= n; j++)
		dp[0][j]= 0;
	
	//状态转移方程
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			if(A[i]==B[j])
				dp[i][j] = dp[i-1][j-1] + 1;
			else
				dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
		}
	}
	
	printf("%d\n", dp[n][n]);		//dp[n][n]是答案
	
	return 0;
}

关于为什么可以转化成LIS问题,这里提供一个解释。

A:3 2 1 4 5

B:1 2 3 4 5

我们不妨给它们重新标个号:把3标成a,把2标成b,把1标成c……于是变成:

A:a b c d e

B:c b a d e

这样标号之后,LCS长度显然不会改变,但是出现了一个性质:

两个序列的子序列,一定是 A A A 的子序列。而 A A A 本身就是单调递增的,因此这个子序列是单调递增的。

换句话说,只要这个子序列在 B B B 中单调递增,它就是 A A A 的子序列。

哪个最长呢?当然是 B B B 的 LIS。

自此完成转化。

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),AC代码:

#include
#include
#include
using namespace std;
const int N = 100010;
int h[N], a[N], dp[N];

int main()
{
	int n;
	scanf("%d", &n);
	
    for(int i = 1; i <= n; ++i)
    {
    	int x;
    	scanf("%d", &x);
    	h[x] = i;
	}
	
	for(int i = 1; i <= n; ++i)
    {
    	int x;
    	scanf("%d", &x);
    	a[i] = h[x];
	}
    
	int len = 1;
    dp[1] = a[1];
	for(int i = 2; i <= n; ++i)
    {
        if(a[i]>dp[len])
			dp[++len] = a[i];
        else
            *lower_bound(dp+1, dp+len+1, a[i]) = a[i];
    }
    cout<<len<<endl;
    
    return 0;
}

你可能感兴趣的:(线性DP)