AcWing 272. 最长公共上升子序列
题目
名字就是题目,给出两个序列A,B,求最长公共上升子序列。(n < 3e3)
分析
线性dp。
①: 状态表示(经验)
- 集合: d p [ i ] [ j ] dp[i][j] dp[i][j] 表示所有在 A [ 1.. i ] A[1..i] A[1..i] 和 b [ 1.. j ] b[1..j] b[1..j] 中出现过,且最后以 b [ j ] b[j] b[j] 结尾的最长公共上升子序列的集合,
- 属性:表示集合中长度的最大值
②: 状态转移
-
不包括 a [ i ] a[i] a[i],-----> d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]
-
包括 a [ i ] a[i] a[i](继续划分),枚举符合条件的 k k k (b[k] < b[j] && k < j) ----> d p [ i − 1 ] [ k ] dp[i-1][k] dp[i−1][k]
复杂度 O ( n 3 ) O(n3) O(n3)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f;
const int N = 3e3 + 5;
int n, a[N], b[N];
int dp[N][N];
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 = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
dp[i][j] = dp[i-1][j]; // 不包含
if (a[i] == b[j]) {
int maxv = 1;
for (int k = 1; k < j; k++)
if (b[k] < b[j])
maxv = max(maxv, dp[i-1][k] + 1);
dp[i][j] = max(maxv, dp[i][j]);
}
}
int res = 0;
for (int i = 1; i <= n; i++) // 答案枚举一下
res = max(res, dp[n][i]);
printf("%d\n", res);
return 0;
}
优化
dp 问题的优化都是对代码做等价变形。
对于这个问题,可以观察发现含有 k k k 的那重循环目的是找所有小于 b [ j ] b[j] b[j] (也即是 a [ i ] a[i] a[i]) 的点,其实不用对每个 b [ j ] b[j] b[j] 重新找一遍,维护一个前缀最值即可去掉循环。 O ( n 2 ) O(n2) O(n2)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f;
const int N = 3e3 + 5;
int n, a[N], b[N];
int dp[N][N];
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 = 1; i <= n; i++) {
int maxv = 1;
for (int j = 1; j <= n; j++) {
dp[i][j] = dp[i - 1][j];
if (a[i] == b[j]) {
dp[i][j] = max(maxv, dp[i][j]);
}
if (b[j] < a[i]) maxv = max(maxv, dp[i-1][j] + 1);
}
}
int res = 0;
for (int i = 1; i <= n; i++)
res = max(res, dp[n][i]);
printf("%d\n", res);
return 0;
}