算法知识图谱总览 / 离线与杂项技巧族 / 有限状态机

有限状态机

P2advancedstandardG-E-7

What · 是什么

FSM用于字符串匹配/自动机DP/AC自动机基础,竞赛中处理模式匹配问题

有限状态机(Finite State Machine, FSM)是描述'按字符逐一转移状态'的数学模型。DFA 是五元组 (Q, Σ, δ, q0, F):有限状态集、字符集、转移函数、起始状态、接受状态集。DFA 接受的语言恰为正则语言;NFA 与其等价(幂集构造)。竞赛应用:判断二进制数奇偶/偶长串、KMP 自动机、AC 自动机(Trie+失配链接)、后缀自动机、序列自动机、DP 套 DP(内层 DP 状态建自动机外层 DP 计数)。

Why · 为什么

直觉 · Intuition

把'问题的所有中间可能'编码成图的状态,每个输入字符是一次转移。判定问题变成'按图走到底看停在哪'。例如判断二进制数奇偶只需两个状态(末位0/1)。自动机的价值在于把子序列/模式匹配这类'本需回溯'的问题变成 O(n) 的线性游走。

原理 · Principle

DFA 计算:从 q0 开始,对每个字符 w_i 执行 δ(r_i, w_i)=r_{i+1},读完若 r_n ∈ F 则接受。NFA 转移返回状态集,等价于并行运行多个分支,可用幂集构造转成 DFA。复杂度:DFA 模拟 O(n),朴素 NFA O(n·s²),bitset 优化到 O(n·s²/w)。正则语言满足封闭性(并/交/补/连接/闭包/同态等),Myhill–Nerode 定理给出最小 DFA 状态数=等价类数。

历史 · History

自动机理论源自 20 世纪中叶计算理论奠基人(Church, Turing, Kleene 等)的研究。FSM 作为最简单的计算模型,是正则语言与编译原理的词法分析基础。竞赛中 OI Wiki 专门设立 FSM 页面,强调在学习 KMP/AC 自动机/SAM 前先掌握自动机思想,并收录 DFA 最小化(Hopcroft 算法)与 DP 套 DP 等进阶内容。

How · 实现

伪代码

# 一般 DFA 模拟
def run_dfa(delta, q0, accept, s):
  q = q0
  for ch in s:
    q = delta[q][ch]
  return q in accept

# 例: 判断二进制串是否为偶数(从左到右, 末位决定)
# 状态 0: 已处理前缀对应偶数, 状态 1: 奇数
# 转移: 读0偶数保持偶, 读1变奇; 奇读0变偶, 奇读1保持奇

Python 实现

def is_even_binary(bits):
    """DFA 判定二进制串是否表示偶数。
    状态 q=0 表示当前前缀是偶数, q=1 表示奇数。
    """
    q = 0
    for ch in bits:
        if ch not in '01':
            raise ValueError('非法字符')
        if q == 0:
            q = 0 if ch == '0' else 1
        else:
            q = 0 if ch == '0' else 1
    return q == 0


def build_kmp_automaton(pattern):
    """构造 KMP 自动机。
    状态 i 表示已匹配 pattern 前 i 个字符;
    delta[i][c] 为读入 c 后应到的状态。
    用失配函数实现(简化版)。
    """
    m = len(pattern)
    # 前缀函数
    pi = [0] * m
    for i in range(1, m):
        j = pi[i - 1]
        while j > 0 and pattern[i] != pattern[j]:
            j = pi[j - 1]
        if pattern[i] == pattern[j]:
            j += 1
        pi[i] = j
    # 字符集
    alpha = set(pattern)
    delta = {i: {c: 0 for c in alpha} for i in range(m + 1)}
    for i in range(m + 1):
        for c in alpha:
            if i < m and c == pattern[i]:
                delta[i][c] = i + 1
            elif i == 0:
                delta[0][c] = 0
            else:
                delta[i][c] = delta[pi[i - 1]][c]
    return delta, m


def kmp_count(text, pattern):
    """用自动机统计 pattern 出现次数(含重叠)。"""
    delta, m = build_kmp_automaton(pattern)
    q = 0
    cnt = 0
    for ch in text:
        if ch in delta[q]:
            q = delta[q][ch]
        else:
            q = 0
        if q == m:
            cnt += 1
    return cnt


if __name__ == '__main__':
    # 二进制偶数判定
    assert is_even_binary('0') is True
    assert is_even_binary('1') is False
    assert is_even_binary('1010') is True
    assert is_even_binary('101') is False
    assert all(is_even_binary(f'{i:b}') == (i % 2 == 0) for i in range(30))

    # KMP 自动机计数
    assert kmp_count('aaaa', 'aa') == 3
    assert kmp_count('ababcabab', 'ab') == 4   # 位置 0,2,5,7
    assert kmp_count('abc', 'def') == 0
    assert kmp_count('abcabc', 'abc') == 2
    assert kmp_count('abcababcabc', 'abc') == 3  # 位置 0,6,8(重叠)

    print('binary even check (0..29): OK')
    print('kmp automaton counts:')
    print('  aaaa/#aa  =', kmp_count('aaaa', 'aa'))
    print('  ababcabab/#ab =', kmp_count('ababcabab', 'ab'))
    print('  abcabc/#abc= ', kmp_count('abcabc', 'abc'))
    print('All fsm tests passed!')
PyPy 提示自动机 DP 大状态常用 dict 存转移或 list 索引; KMP/SAM 自动机用数组 delta 时 PyPy 快于 dict; 大规模自动机 DP 用递推而非递归。
时间复杂度DFA 模拟 O(N);KMP 自动机构造 O(M|Σ|);朴素 NFA O(N·S²)
空间复杂度O(S·|Σ|) — 转移表; NFA 需存状态集
注意 · 局限自动机只接受正则语言, 无法识别非正则语言(如 a^n b^n); 状态数指数爆炸场景(幂集构造)需谨慎; 单纯 FSM 不是算法, 具体实现与优化(表/递归)影响常数; 处理任意模式匹配仍需 KMP/AC 等特定构造。

易错点

例题

OJ题号难度考点链接
LuoguP122945THUPC2025决赛 01串三目运算: Myhill–Nerode定理建自动机+倍增/猫树https://www.luogu.com.cn/problem/P12294
LuoguP104365JOISC2024 卡牌收集: 自动机状态压缩+区间查询https://www.luogu.com.cn/problem/P10436
Codeforces956F5Minimal Subset Difference: 内层DP建自动机+DFA最小化, DP套DP经典https://codeforces.com/contest/956/problem/F
步骤迷你图(节点 = 算法步骤 · 边 = 流程 · 绿色 = 起始步骤)

信源

✓ = 已核验 · △ = 未核验