Skip to content

longest_common_subsequence.hpp

SECTIONDP INCLUDEnoya/longest_common_subsequence.hpp

求两个序列的一组最长公共子序列匹配下标;适合需要恢复方案而不只求长度的题目。

Complexity: Time: O(nm) for a certificate; bit-parallel length uses O(n log n + d ceil(n/64) + m(log d + ceil(n/64))) time and Space: O(nm) for a certificate or O(d ceil(n/64)) for bit-parallel length, where d is the number of distinct first-sequence values.

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include

/// @complexity Time: O(nm) for a certificate; bit-parallel length uses
/// O(n log n + d ceil(n/64) + m(log d + ceil(n/64))) time and
/// Space: O(nm) for a certificate or O(d ceil(n/64)) for bit-parallel length,
/// where d is the number of distinct first-sequence values.

#include <algorithm>
#include <bit>
#include <cstdint>
#include <utility>
#include <vector>

namespace noya {

/// @brief Return matched index pairs for one longest common subsequence in
/// O(nm) time and memory.
template <class First, class Second>
std::vector<std::pair<int, int>> longest_common_subsequence(const First &a,
                                                            const Second &b) {
  int n = int(a.size());
  int m = int(b.size());
  std::vector dp(n + 1, std::vector<int>(m + 1));
  for (int i = n - 1; i >= 0; i--) {
    for (int j = m - 1; j >= 0; j--) {
      if (a[i] == b[j]) {
        dp[i][j] = dp[i + 1][j + 1] + 1;
      } else {
        dp[i][j] = std::max(dp[i + 1][j], dp[i][j + 1]);
      }
    }
  }
  std::vector<std::pair<int, int>> res;
  int i = 0;
  int j = 0;
  while (i < n && j < m) {
    if (a[i] == b[j] && dp[i][j] == dp[i + 1][j + 1] + 1) {
      res.emplace_back(i++, j++);
    } else if (dp[i + 1][j] >= dp[i][j + 1]) {
      i++;
    } else {
      j++;
    }
  }
  return res;
}

/// @brief Return only the LCS length using the bit-parallel DP-row-dif
/// update. Consecutive cells of a standard LCS DP row differ by zero or one,
/// so the complete row is represented by a bitset D of those differences.
/// For the next value, X marks equal positions in the first sequence. The
/// next row is `D = (D | X) & ~((D | X) - ((D << 1) | 1))`; the loop below
/// performs that subtraction word by word, carrying the shift and borrow
/// across 64-bit blocks. The popcount of the final dif row is the LCS
/// length. This is intended for long sequences when the quadratic certificate
/// table is too large.
template <class Sequence>
int longest_common_subsequence_length_bitset(const Sequence &a,
                                             const Sequence &b) {
  using value_type = typename Sequence::value_type;
  const int n = int(a.size());
  const int blk = (n + 63) / 64;
  if (n == 0 || b.empty()) {
    return 0;
  }

  std::vector<value_type> xs(a.begin(), a.end());
  std::sort(xs.begin(), xs.end());
  xs.erase(std::unique(xs.begin(), xs.end()), xs.end());
  std::vector<std::vector<std::uint64_t>> mat(xs.size(),
                                              std::vector<std::uint64_t>(blk));
  for (int pos = 0; pos < n; pos++) {
    int id = int(std::lower_bound(xs.begin(), xs.end(), a[pos]) - xs.begin());
    mat[id][pos / 64] |= std::uint64_t(1) << (pos % 64);
  }

  std::vector<std::uint64_t> dif(blk);
  for (const value_type &val : b) {
    auto it = std::lower_bound(xs.begin(), xs.end(), val);
    const std::vector<std::uint64_t> *eq =
        it != xs.end() && *it == val ? &mat[it - xs.begin()] : nullptr;
    std::uint64_t sc = 1;
    std::uint64_t brw = 0;
    for (int bl1 = 0; bl1 < blk; bl1++) {
      std::uint64_t old = dif[bl1];
      std::uint64_t sum = old | (eq ? (*eq)[bl1] : 0);
      std::uint64_t sh = (old << 1) | sc;
      sc = old >> 63;
      std::uint64_t sub = sum - sh - brw;
      std::uint64_t nb = sum < sh || (brw != 0 && sum == sh);
      dif[bl1] = sum & ~sub;
      brw = nb;
    }
  }

  int res = 0;
  for (std::uint64_t bl1 : dif) {
    res += std::popcount(bl1);
  }
  return res;
}

} // namespace noya
#ifndef NOYA_LONGEST_COMMON_SUBSEQUENCE_HPP
#define NOYA_LONGEST_COMMON_SUBSEQUENCE_HPP 1

/// @complexity Time: O(nm) for a certificate; bit-parallel length uses
/// O(n log n + d ceil(n/64) + m(log d + ceil(n/64))) time and
/// Space: O(nm) for a certificate or O(d ceil(n/64)) for bit-parallel length,
/// where d is the number of distinct first-sequence values.

#include <algorithm>
#include <bit>
#include <cstdint>
#include <utility>
#include <vector>

namespace noya {

/// @brief Return matched index pairs for one longest common subsequence in
/// O(nm) time and memory.
template <class First, class Second>
std::vector<std::pair<int, int>> longest_common_subsequence(const First &a,
                                                            const Second &b) {
  int n = int(a.size());
  int m = int(b.size());
  std::vector dp(n + 1, std::vector<int>(m + 1));
  for (int i = n - 1; i >= 0; i--) {
    for (int j = m - 1; j >= 0; j--) {
      if (a[i] == b[j]) {
        dp[i][j] = dp[i + 1][j + 1] + 1;
      } else {
        dp[i][j] = std::max(dp[i + 1][j], dp[i][j + 1]);
      }
    }
  }
  std::vector<std::pair<int, int>> res;
  int i = 0;
  int j = 0;
  while (i < n && j < m) {
    if (a[i] == b[j] && dp[i][j] == dp[i + 1][j + 1] + 1) {
      res.emplace_back(i++, j++);
    } else if (dp[i + 1][j] >= dp[i][j + 1]) {
      i++;
    } else {
      j++;
    }
  }
  return res;
}

/// @brief Return only the LCS length using the bit-parallel DP-row-dif
/// update. Consecutive cells of a standard LCS DP row differ by zero or one,
/// so the complete row is represented by a bitset D of those differences.
/// For the next value, X marks equal positions in the first sequence. The
/// next row is `D = (D | X) & ~((D | X) - ((D << 1) | 1))`; the loop below
/// performs that subtraction word by word, carrying the shift and borrow
/// across 64-bit blocks. The popcount of the final dif row is the LCS
/// length. This is intended for long sequences when the quadratic certificate
/// table is too large.
template <class Sequence>
int longest_common_subsequence_length_bitset(const Sequence &a,
                                             const Sequence &b) {
  using value_type = typename Sequence::value_type;
  const int n = int(a.size());
  const int blk = (n + 63) / 64;
  if (n == 0 || b.empty()) {
    return 0;
  }

  std::vector<value_type> xs(a.begin(), a.end());
  std::sort(xs.begin(), xs.end());
  xs.erase(std::unique(xs.begin(), xs.end()), xs.end());
  std::vector<std::vector<std::uint64_t>> mat(xs.size(),
                                              std::vector<std::uint64_t>(blk));
  for (int pos = 0; pos < n; pos++) {
    int id = int(std::lower_bound(xs.begin(), xs.end(), a[pos]) - xs.begin());
    mat[id][pos / 64] |= std::uint64_t(1) << (pos % 64);
  }

  std::vector<std::uint64_t> dif(blk);
  for (const value_type &val : b) {
    auto it = std::lower_bound(xs.begin(), xs.end(), val);
    const std::vector<std::uint64_t> *eq =
        it != xs.end() && *it == val ? &mat[it - xs.begin()] : nullptr;
    std::uint64_t sc = 1;
    std::uint64_t brw = 0;
    for (int bl1 = 0; bl1 < blk; bl1++) {
      std::uint64_t old = dif[bl1];
      std::uint64_t sum = old | (eq ? (*eq)[bl1] : 0);
      std::uint64_t sh = (old << 1) | sc;
      sc = old >> 63;
      std::uint64_t sub = sum - sh - brw;
      std::uint64_t nb = sum < sh || (brw != 0 && sum == sh);
      dif[bl1] = sum & ~sub;
      brw = nb;
    }
  }

  int res = 0;
  for (std::uint64_t bl1 : dif) {
    res += std::popcount(bl1);
  }
  return res;
}

} // namespace noya

#endif // NOYA_LONGEST_COMMON_SUBSEQUENCE_HPP
#include <algorithm>
#include <bit>
#include <cstdint>
#include <utility>
#include <vector>

/// @complexity Time: O(nm) for a certificate; bit-parallel length uses
/// O(n log n + d ceil(n/64) + m(log d + ceil(n/64))) time and
/// Space: O(nm) for a certificate or O(d ceil(n/64)) for bit-parallel length,
/// where d is the number of distinct first-sequence values.

namespace noya {

/// @brief Return matched index pairs for one longest common subsequence in
/// O(nm) time and memory.
template <class First, class Second>
std::vector<std::pair<int, int>> longest_common_subsequence(const First &a,
                                                            const Second &b) {
  int n = int(a.size());
  int m = int(b.size());
  std::vector dp(n + 1, std::vector<int>(m + 1));
  for (int i = n - 1; i >= 0; i--) {
    for (int j = m - 1; j >= 0; j--) {
      if (a[i] == b[j]) {
        dp[i][j] = dp[i + 1][j + 1] + 1;
      } else {
        dp[i][j] = std::max(dp[i + 1][j], dp[i][j + 1]);
      }
    }
  }
  std::vector<std::pair<int, int>> res;
  int i = 0;
  int j = 0;
  while (i < n && j < m) {
    if (a[i] == b[j] && dp[i][j] == dp[i + 1][j + 1] + 1) {
      res.emplace_back(i++, j++);
    } else if (dp[i + 1][j] >= dp[i][j + 1]) {
      i++;
    } else {
      j++;
    }
  }
  return res;
}

/// @brief Return only the LCS length using the bit-parallel DP-row-dif
/// update. Consecutive cells of a standard LCS DP row differ by zero or one,
/// so the complete row is represented by a bitset D of those differences.
/// For the next value, X marks equal positions in the first sequence. The
/// next row is `D = (D | X) & ~((D | X) - ((D << 1) | 1))`; the loop below
/// performs that subtraction word by word, carrying the shift and borrow
/// across 64-bit blocks. The popcount of the final dif row is the LCS
/// length. This is intended for long sequences when the quadratic certificate
/// table is too large.
template <class Sequence>
int longest_common_subsequence_length_bitset(const Sequence &a,
                                             const Sequence &b) {
  using value_type = typename Sequence::value_type;
  const int n = int(a.size());
  const int blk = (n + 63) / 64;
  if (n == 0 || b.empty()) {
    return 0;
  }

  std::vector<value_type> xs(a.begin(), a.end());
  std::sort(xs.begin(), xs.end());
  xs.erase(std::unique(xs.begin(), xs.end()), xs.end());
  std::vector<std::vector<std::uint64_t>> mat(xs.size(),
                                              std::vector<std::uint64_t>(blk));
  for (int pos = 0; pos < n; pos++) {
    int id = int(std::lower_bound(xs.begin(), xs.end(), a[pos]) - xs.begin());
    mat[id][pos / 64] |= std::uint64_t(1) << (pos % 64);
  }

  std::vector<std::uint64_t> dif(blk);
  for (const value_type &val : b) {
    auto it = std::lower_bound(xs.begin(), xs.end(), val);
    const std::vector<std::uint64_t> *eq =
        it != xs.end() && *it == val ? &mat[it - xs.begin()] : nullptr;
    std::uint64_t sc = 1;
    std::uint64_t brw = 0;
    for (int bl1 = 0; bl1 < blk; bl1++) {
      std::uint64_t old = dif[bl1];
      std::uint64_t sum = old | (eq ? (*eq)[bl1] : 0);
      std::uint64_t sh = (old << 1) | sc;
      sc = old >> 63;
      std::uint64_t sub = sum - sh - brw;
      std::uint64_t nb = sum < sh || (brw != 0 && sum == sh);
      dif[bl1] = sum & ~sub;
      brw = nb;
    }
  }

  int res = 0;
  for (std::uint64_t bl1 : dif) {
    res += std::popcount(bl1);
  }
  return res;
}

} // namespace noya