首页 > 技术文章 > 「排列最长公共子序列」P1439 【模板】最长公共子序列

luckyblock 2020-06-14 09:45 原文

知识点: 线性DP,LCS,LIS

原题面

题目要求:

给定 \(1,2,\dots n\) 的两个排列 \(a,b\),求其最长公共子序列。
\(n\le 10^5\)


分析题意

算法一

有一个极其显然的做法。
\(f_{i,j}\) 为,\(a\) 中匹配到第\(i\) 位,\(b\) 中匹配到第 \(j\) 位时,最长公共子序列的长度。

讨论 \(a_i\)\(b_j\) 是否相等,则有:
\(f_{i,j} = \begin{cases}f_{i-1,j-1} +1 & a_i=b_j\\\max ({f_{i,j-1}, f_{i-1,j}}) & \text{otherwise}\end{cases}\)

复杂度 \(O(n^2)\),期望得分 \(50\text{pts}\)


算法二

上述算法已无法再优化,考虑奇技淫巧。

排列中不存在重复元素,考虑建立一一映射关系。
\(pos_{a_i} = i\),使 \(a_i \Rightarrow i, b_i \Rightarrow pos_{b_i}\)
即发生如下的转化:

\[\begin{aligned}a&:3\ 1\ 5\ 4\ 2\\b&:2\ 3\ 4\ 5\ 1\end{aligned} \Longrightarrow \begin{aligned}&a:1\ 2\ 3\ 4\ 5\\&b:5\ 1\ 4\ 3\ 2\end{aligned} \]

显然映射后的 LCS 的长度不会受影响。

由于 \(a_i\) 单调递增,则 LCS 也单调递增。
换言之,\(b\) 中所有单调递增子序列,均是一公共子序列。
问题转化为,求 \(b\) 最长的单调递增子序列的长度。
即经典的 LIS 问题。

使用 \(O(n\log n)\) 算法求解即可,期望得分 \(\text{100pts}\)


代码实现

\(O(n\log n)\ \text{100pts}\)

//
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <algorithm>
#define ll long long
const int kMaxn = 1e5 + 10;
//=============================================================
int n, a[kMaxn], b[kMaxn], map[kMaxn];
int lth, f[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
int Search(int val) {
  int l = 0, r = lth;
  while (l < r) {
    int mid = (l + r) >> 1;
    if (f[mid] > val) r = mid;
    else l = mid + 1;
  }
  return l;
}
//=============================================================
int main() {
  n = read();
  for (int i = 1; i <= n; ++ i) {
    a[i] = read(); map[a[i]] = i; 
  }
  for (int i = 1; i <= n; ++ i) b[i] = map[read()];
  
  lth = 1; 
  f[1] = b[1];
  for (int i = 2; i <= n; ++ i) {
    if (b[i] > f[lth]) {
      f[++ lth] = b[i];
      continue;
    }
    int pos = Search(b[i]);
    f[pos] = std :: min(f[pos], b[i]);
  }
  printf("%d\n", lth);
  return 0;
}

\(O(n^2)\ \text{50pts}\)

#include <cstdio>
#include <ctype.h>
#include <algorithm>
#define ll long long
const int kMaxn = 1e3 + 10;
//=============================================================
int n, a[kMaxn], b[kMaxn], f[kMaxn][kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
//=============================================================
int main() {
  n = read();
  for (int i = 1; i <= n; ++ i) a[i] = read();
  for (int i = 1; i <= n; ++ i) b[i] = read();
  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= n; ++ j) {
      f[i][j] = std :: max(f[i][j - 1], f[i - 1][j]);
      if (a[i] == b[j]) f[i][j] = std :: max(f[i][j], f[i - 1][j - 1] + 1);
    }
  }
  printf("%d\n", f[n][n]);
  return 0;
}

推荐阅读