暴力递归能更好地帮助我们从宏观上理解题目,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] 的情况,分为两种决策:
若 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[i−1][j−1]+1。
若 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[i−1][j] 与 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j−1] 中的较大值,即有 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[i−1][j], dp[i][j−1]}。
由此可以得到状态转移方程:
边界: 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;
}