题解 AT4379 【[AGC027E] ABBreviate】

· · 题解

题意简述

给定一个只含小写字母 \mathtt{a}, \mathtt{b} 的字符串 s,每次你可以执行以下两种操作:

  1. 选取 s 中连续的两个字符 \mathtt{aa},把它们删去,替换成一个字符 \mathtt{b}
  2. 选取 s 中连续的两个字符 \mathtt{bb},把它们删去,替换成一个字符 \mathtt{a}

请你求出执行若干次操作后,能够得到的本质不同的字符串有多少个,答案对 ({10}^9 + 7) 取模。

题解

我们考虑判断一个字符串 t 能否通过 s 变换得到。

假设可以,则对于 t 的每一个字符,都对应了 s 中的一段连续区间。

也就是,s 的一个子串,经过若干次操作后,变成了单一的一个字符 \mathtt{a}\mathtt{b}

这里有个很有趣的结论:如果我们把 \mathtt{a}, \mathtt{b} 分别看作 1, 2,则执行操作是不会改变所有字符之和模 3 的结果的。

也就是说,定义 p(s) 为字符串 s 中所有字符之和模 3,则 s 经过若干次操作变成 tp(s)p(t) 是相等的。

那么回到变成字符的问题上来,如何判断 s 能不能变成单一的字符 \mathtt{a}\mathtt{b} 呢?

显然 p(s) 应该等于 1 或者 2,这对应了最终变成的是 \mathtt{a} 或者是 \mathtt{b},显然这是必要条件。

但是这并不是充分条件,考虑 s = \mathtt{ababa},虽然 p(s) = p(\mathtt{a}),但是根本没法对 s 进行操作,所以不可行。

如果 s 中存在两个相邻的相同字符呢?事实证明,再加上这个条件就是充分的了。

考虑归纳:当 |s| \le 2 时成立;否则只要对第一个位置进行操作,就能使长度变小 1 且还满足条件。

也就是说,字符串 \boldsymbol{s} 能变成单个字符 \boldsymbol{c} 当且仅当 \boldsymbol{p(s) = p(c)} 且「\boldsymbol{|s| = 1}\boldsymbol{s} 中存在相邻的相同字符」

那么再回到一开始的问题:字符串 t 能否通过 s 变换得到?

很自然地,有一个贪心匹配的算法:

依次考虑 t 中的每一个字符,选择一个 s最短前缀变换成这个字符,然后删掉这个前缀继续匹配。

举个例子,当 t = \mathtt{abba}s = \mathtt{aabbabaabaaabab} 时:

最后 s = \mathtt{{\color{red}{a}}|{\color{blue}{abb}}|{\color{purple}{abaa}}|{\color{green}{baa}}|abab},被分成了五段(前 |t| 段加上一个未匹配的后缀)。

特别地,这个最后一段需要满足 \boldsymbol{p} 值为 \boldsymbol{0},例如这里 p(\mathtt{abab}) = 0(因为需要满足 p(s) = p(t))。

我们可以证明:s 能变成 t 当且仅当能够成功分段,且 s 存在两个相邻的相同字符(或 t = s)。

注意:证明部分可以酌情跳过

注意:证明部分可以酌情跳过

注意这里包含了两个条件,第一个是成功分段,第二个是 s 存在相邻的相同字符。

我们先考虑 s 不存在相邻的相同字符的情况,显然输出 1 即可(当 t = s 时)。

那么在后续证明中假定 s 存在相邻的相同字符,条件变为:能够成功分段

我们首先证明它的必要性,即任意一个合法的 t 都能够成功分段:

还需要证明它的充分性,即只要能够成功分段,那就一定是合法的:

证明部分结束

证明部分结束

那么我们先特判 s\mathtt{ababababa} 这种形式,直接输出 1,否则考虑做一个这样的 DP:

dp(i) 表示考虑了 s \boldsymbol{i} 开始的后缀,能够成功分段的 t 的数量(注意最后未匹配的后缀的 p = 0 的限制)。

则答案就是 dp(1),转移是比较显然的。

我代码中实现的时候,为了方便,把 t 是空串的情况也计入答案,输出的时候要扣除。

时间复杂度为 \mathcal O (|s|),评测链接。