0%

注意在构造 nxt 数组的时候,一定不要忘记给已经到过的点也要建边

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
\section{错误三:BFS 建自动机时 \texttt{nxt} 表不完整}
%======================================================================

\subsection*{错误代码}
\begin{minted}{cpp}
for (int i = 0; i < 2; ++i) {
auto v = u.next_state(i);
if (mp.contains(v)) {
continue; // 错误:跳过了,但没有记录转移边
}
mp[v] = idx;
nxt[mp[u]][i] = idx;
++idx;
q.push(v);
}
\end{minted}

\subsection*{错误分析}
当目标状态 \texttt{v} 已经在 BFS 中被访问过时,代码直接 \texttt{continue},\textbf{没有设置 \texttt{nxt[mp[u]][i] = mp[v]}}。这导致所有"转移到已访问状态"的边全部留为初始值 $-1$

自动机共 $40$ 个状态、$80$ 条有向边,但其中只有 $39$ 条(BFS 树边)被正确记录,剩下 $41$ 条(回边和交叉边)全部丢失。后续的子序列 DP 在遇到这些 $-1$ 的转移时会直接跳过,导致大量合法的子序列转移被忽略。

\subsection*{修复}
\begin{minted}{cpp}
for (int i = 0; i < 2; ++i) {
auto v = u.next_state(i);
if (mp.contains(v)) {
nxt[mp[u]][i] = mp[v];
continue;
}
mp[v] = idx;
idx_state[idx] = v;
nxt[mp[u]][i] = idx;
++idx;
q.push(v);
}
\end{minted}

\subsection*{教训}
BFS 建图时,"发现新节点"和"记录边"是两个独立的操作。即使目标节点已经在队列中,从当前节点到它的\textbf{转移边}仍然必须被记录。这是 BFS 建自动机的经典易错点。