自动机

简介

自动机是一种通过状态之间的跳转进行计算的数学模型。

当自动机接受一个输入字符时,它使用状态转移函数,依据当前所处的状态和输入的字符跳转至下一个状态。我们常常使用有向图表示一个有限状态自动机。此时,状态在有向图上以结点形式表示;状态转移函数表示为这张图上的有向边的集合。

比如说判断一个二进制串的奇偶性,我们可以建出以下这个自动机:

image

这个自动机初始状态为 \(q_0\),如果最终状态为 \(q_0\),则该二进制串为偶数,否则为奇数。

子序列自动机

首先考虑怎么判断一个字符串 \(T\) 是否是字符串 \(S\) 的子序列。

可以用类似于贪心的方法:令 \(q_i\) 表示当前字符串匹配到了 \(S\) 的第 \(i\) 个字符。\(q_{0}\) 为初始状态。\(q_{-1}\) 表示当前字符串不是 \(S\) 的子序列。而转移 \(\delta(q_i,c)\) 就为 \(i\) 之后第一次字符 \(c\) 的出现位置。若没有,则为 \(-1\)

例如 \(S=abaab\) 时自动机如下:

image

这时我们可以发现,所有在这个自动机上以 \(q_0\) 为起点,不以 \(q_{-1}\) 为终点的路径都对应着一个子序列,并且这些子序列都是本质不同的。

什么是本质不同子序列 本质不同子序列是指两个长度不等存在字符不等的子序列。即使这两个子序列在原字符串中的下标不同,只要两个字符串相等,那么它们就是本质相同的。

原因很简单:因为自动机中是取最近的一个字符,所以就不会有重复。

然后在这个自动机上做 \(dp\) 即可求出 \(S\) 的本质不同子序列数量。

时空复杂度均为 \(O(N\cdot |\sum|)\)

代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 100005, MOD = int(1e9) + 7;

int t, n, dp[MAXN], ans, r[MAXN][26];
string s;

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> s;
  n = s.size(), s = ' ' + s;
  fill(&r[n + 1][0], &r[n + 1][26], n + 1);
  for(int i = n; i >= 0; --i) {
    dp[i] = 0;
    for(int j = 0; j < 26; ++j) {
      r[i][j] = r[i + 1][j];
    }
    if(i < n) {
      r[i][s[i + 1] - 'A'] = i + 1;
    }
  }
  dp[0] = 1, ans = 0;
  for(int i = 0; i <= n; ++i) {
    for(int j = 0; j < 26; ++j) {
      dp[r[i][j]] = (dp[r[i][j]] + dp[i]) % MOD;
    }
    ans = (ans + dp[i]) % MOD;
  }
  cout << ans << "\n";
  return 0;
}

KMP 与前缀自动机

KMP

给定字符串 \(S\),定义 \(P_i\) 表示 \(S\) 长度为 \(i\) 的前缀的 border (最长真前缀等于真后缀) 的长度。

例如 \(S=aabaaab\) 时,\(P=[0, 1, 0, 1, 2, 2, 3]\)

假设我们已经知道了 \(P_1,P_2,\dots,P_{i-1}\),如何求出 \(P_i\) 呢?

image

这张图中红色部分是前缀 \(i-1\),绿色部分是 \(P_{i-1}\),蓝色部分是 \(P_{P_{i-1}}\)。如果这时 \(S_{P_{i-1}+1}=S_{i}\),则 \(P_{i}\)\(P_{i-1}+1\)。否则再判断 \(P_{P_{i-1}}\),直到找到相等的位置即可。

时空复杂度均为 \(O(N)\)

前缀自动机

记模式串 \(S\) 的长度为 \(n\),构造一个包含 \(q_0, q_1, \dots, q_n\) 共计 \(n + 1\)
个状态的自动机,状态 \(q_i\) 对应匹配至模式串 \(T\) 的第 \(i\) 个字符。

很容易得到以下转移:\(\delta(q_i,c)=\begin{cases}q_{i+1}&i<n\wedge S_{i+1}=c\\\delta(q_{P_i},c)&\text{otherwise}\end{cases}\)

例如当 \(S=abaab\) 时自动机如下:

image

也可以通过前缀自动机反推 \(P_i\)

时空复杂度均为 \(O(N\cdot |\sum|)\)

代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 1000015;

int n, p[MAXN], nxt[MAXN][26];
string s;

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> s >> q;
  n = s.size(), s = ' ' + s + ' ';
  nxt[0][s[1] - 'a'] = 1;
  for(int i = 1; i < n; ++i) {
    for(int j = 0; j < 26; ++j) {
      if(i < n && j == s[i + 1] - 'a') {
        nxt[i][j] = i + 1, p[i + 1] = nxt[p[i]][j];
      }else {
        nxt[i][j] = nxt[p[i]][j];
      }
    }
  }
  for(int i = 1; i <= n; ++i) {
    cout << p[i] << " ";
  }
  return 0;
}

posted @ 2024-07-20 14:43  Yaosicheng124  阅读(16)  评论(0编辑  收藏  举报