Sequence Automaton

Before reading this article, read Automata first.

Definition

A sequence automaton is an automaton that accepts exactly the subsequences of one string.

Throughout this article, ss denotes that string.

States

If ss contains nn characters, the sequence automaton has n+1n+1 states.

Let tt be a subsequence of ss. Then δ(start,t)\delta(start, t) is the position where tt ends at its first occurrence in ss.

In other words, state ii represents the set difference between the subsequences of the prefix s[1..i]s[1..i] and those of the prefix s[1..i−1]s[1..i-1].

Every state of a sequence automaton is an accepting state.

Transitions

The state definition gives δ(u,c)=min⁡{i∣i>u,s[i]=c}\delta(u, c)=\min\{i|i>u,s[i]=c\}, the position of the next occurrence of character cc.

Why the “next” occurrence? If i>ji>j, the subsequences of the suffix s[i..∣s∣]s[i..|s|] form a subset of those of s[j..∣s∣]s[j..|s|], so choosing the earliest possible occurrence is always optimal.

Implementation

Scan from right to left while maintaining the earliest occurrence of each character:

1Input. A string S2Output. The state transition of the sequence automaton of S3Method. 4for c∈Σ5next[c]←null6for i←∣S∣ downto 17next[S[i]]←i8for c∈Σ9δ(i−1,c)←next[c]10return δ\begin{array}{ll} 1 & \textbf{Input. } \text{A string } S\\ 2 & \textbf{Output. } \text{The state transition of the sequence automaton of }S \\ 3 & \textbf{Method. } \\ 4 & \textbf{for }c\in\Sigma\\ 5 & \qquad next[c]\gets null\\ 6 & \textbf{for }i\gets|S|\textbf{ downto }1\\ 7 & \qquad next[S[i]]\gets i\\ 8 & \qquad \textbf{for }c\in\Sigma\\ 9 & \qquad\qquad \delta(i-1,c)\gets next[c]\\ 10 & \textbf{return }\delta \end{array}

The construction takes O(n∣Σ∣)O(n|\Sigma|) time.

Examples

「HEOI2015」Shortest Non-Common Substring

Given two strings AA and BB consisting of lowercase English letters (1≤∣A∣,∣B∣≤20001\le |A|, |B|\le 2000), find:

  1. A shortest substring of AA that is not a substring of BB;
  2. A shortest substring of AA that is not a subsequence of BB;
  3. A shortest subsequence of AA that is not a substring of BB;
  4. A shortest subsequence of AA that is not a subsequence of BB.
Solution

Questions 1 and 3 require a suffix automaton and use similar methods, so only questions 2 and 4 are discussed here.

Question 2 is straightforward: enumerate substrings of f(i,j)f(i, j), feed them into the sequence automaton of ii, and count those that are not accepted.

Question 4 requires DP. Let jj be the minimum number of additional characters needed to obtain a non-common subsequence when the sequence automaton of f(i,null)=0f(i, \textit{null})=0 is in state and that of is in state . The transition is

f(i,j)=min⁡δA(i,c)≠nullf(δA(i,c),δB(j,c))+1.f(i, j)=\min_{\delta_A(i,c)\ne \textit{null}}f(\delta_A(i, c), \delta_B(j, c))+1.

The starting condition is .

Reference Code
#include <algorithm>
#include <cstring>
#include <iostream>
#include <string>
using namespace std;

constexpr int N = 2005;

string s, t;
int na[N][26], nb[N][26], nxt[26];
int n, m, a[N], b[N], tot = 1, p = 1, f[N][N << 1];

struct SAM {
  int par, ch[26], len;
} sam[N << 1];

void insert(int x) {
  int np = ++tot;  // new node
  sam[np].len = sam[p].len + 1;
  while (p && !sam[p].ch[x]) {
    sam[p].ch[x] = np;
    p = sam[p].par;
  }
  if (p == 0)
    sam[np].par = 1;
  else {
    int q = sam[p].ch[x];
    if (sam[q].len == sam[p].len + 1)
      sam[np].par = q;
    else {
      int nq = ++tot;
      sam[nq].len = sam[p].len + 1;
      memcpy(sam[nq].ch, sam[q].ch, sizeof(sam[q].ch));
      sam[nq].par = sam[q].par;
      sam[q].par = sam[np].par = nq;
      while (p && sam[p].ch[x] == q) {
        sam[p].ch[x] = nq;
        p = sam[p].par;
      }
    }
  }
  p = np;
}

int main() {
  cin >> s >> t;

  n = s.size();
  m = t.size();
  s = " " + s;
  t = " " + t;

  for (int i = 1; i <= n; ++i) a[i] = s[i] - 'a';
  for (int i = 1; i <= m; ++i) b[i] = t[i] - 'a';

  for (int i = 1; i <= m; ++i) insert(b[i]);

  // nxt[S[i]] <- i
  for (int i = 0; i < 26; ++i) nxt[i] = n + 1;
  for (int i = n; i >= 0; --i) {
    memcpy(na[i], nxt, sizeof(nxt));
    nxt[a[i]] = i;
  }

  for (int i = 0; i < 26; ++i) nxt[i] = m + 1;
  for (int i = m; i >= 0; --i) {
    memcpy(nb[i], nxt, sizeof(nxt));
    nxt[b[i]] = i;
  }

  // Compute the answers for the four cases.
  // 1
  int ans = N;
  for (int l = 1; l <= n; ++l) {
    for (int r = l, u = 1; r <= n; ++r) {
      u = sam[u].ch[a[r]];
      if (!u) {
        ans = min(ans, r - l + 1);
        break;
      }
    }
  }

  cout << (ans == N ? -1 : ans) << '\n';

  // 2
  ans = N;

  for (int l = 1; l <= n; ++l) {
    for (int r = l, u = 0; r <= n; ++r) {
      u = nb[u][a[r]];
      if (u == m + 1) {
        ans = min(ans, r - l + 1);
        break;
      }
    }
  }

  cout << (ans == N ? -1 : ans) << '\n';

  // 3
  for (int i = n; i >= 0; --i) {
    for (int j = 1; j <= tot; ++j) {
      f[i][j] = N;
      for (int c = 0; c < 26; ++c) {
        int u = na[i][c];
        int v = sam[j].ch[c];
        if (u <= n) f[i][j] = min(f[i][j], f[u][v] + 1);
      }
    }
  }

  cout << (f[0][1] == N ? -1 : f[0][1]) << '\n';

  // 4
  memset(f, 0, sizeof(f));

  for (int i = n; i >= 0; --i) {
    for (int j = 0; j <= m; ++j) {
      f[i][j] = N;
      for (int c = 0; c < 26; ++c) {
        int u = na[i][c];
        int v = nb[j][c];
        if (u <= n) f[i][j] = min(f[i][j], f[u][v] + 1);
      }
    }
  }

  cout << (f[0][0] == N ? -1 : f[0][0]) << '\n';

  return 0;
}

This content comes from OI Wiki: Sequence Automaton. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.