2025 谐音替换 · 方案二 叠串+AC自动机
规则对编码成叠串,答案即叠串在询问编码串中的出现次数
正文
// CSP-S 2025 复赛 T3 · 谐音替换(方案二:叠串编码 + AC 自动机,满分)
// 原题:https://oj.yecheng.tv/p/2468
// 题意:同方案一(|t| 总长 ≤ 5e6,需满分口径)。
// 思路(把「能否替换」变成「是否为子串」):
// 对每对 (a, b)(a != b),取首末失配位 l、r,编码成叠串
// P = a[0..l) + '{' + a[l..r] + '{' + b[l..r] + '{' + a(r..]
// 其中 '{' 不在小写字母内,用来挡住跨区间的错误匹配。
// 询问串 (t1, t2) 用同一规则编码后:这对规则可行
// <=> P 是询问编码串的子串。
// 把全部 P 插入 AC 自动机(26 字母 + '{' 共 27 叉),询问时沿
// goto 图走一遍,沿途累加 fail 树上前缀计数即答案。
// 复杂度:预处理 O(总长),每次询问 O(|t|)。内存约 600MB,需 2GB。
// 易错点:
// 1. a == b 的规则对必须跳过,否则 f() 求失配位死循环;
// 2. 询问两串长度不等直接输出 0,不能进编码;
// 3. trie 静态数组 5.7e6 × 27 个 int,开小必 MLE;
// 4. '{' 的编码位是 26('{' - 'a' = -58 溢出,须单独映射)。
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int MAXN = 5700005;
int trie[MAXN][27];
int fail_[MAXN];
int cnt_[MAXN];
int tot = 1;
// '{' 与小写字母统一映射:'{' -> 26
inline int id(char c) { return c == '{' ? 26 : c - 'a'; }
void insert(const string &s) {
int p = 1;
for (char c : s) {
int x = id(c);
if (trie[p][x] == 0) trie[p][x] = ++tot;
p = trie[p][x];
}
cnt_[p]++;
}
void build() {
queue<int> q;
for (int i = 0; i < 27; i++) {
int v = trie[1][i];
if (v) { fail_[v] = 1; q.push(v); }
else trie[1][i] = 1;
}
while (!q.empty()) {
int u = q.front();
q.pop();
cnt_[u] += cnt_[fail_[u]];
for (int i = 0; i < 27; i++) {
int v = trie[u][i];
if (v) { fail_[v] = trie[fail_[u]][i]; q.push(v); }
else trie[u][i] = trie[fail_[u]][i];
}
}
}
ll check(const string &s) {
ll res = 0;
int p = 1;
for (char c : s) {
p = trie[p][id(c)];
res += cnt_[p];
}
return res;
}
// a = x + y + z, b = x + y' + z -> x + '{' + y + '{' + y' + '{' + z
string f(const string &a, const string &b) {
int siz = (int)a.size();
int l = 0, r = siz - 1;
while (a[l] == b[l]) l++;
while (a[r] == b[r]) r--;
string res;
res.append(a, 0, l);
res.push_back('{');
res.append(a, l, r - l + 1);
res.push_back('{');
res.append(b, l, r - l + 1);
res.push_back('{');
res.append(a, r + 1, siz - r - 1);
return res;
}
static char buf[5000005 + 10];
int main() {
int n, q;
scanf("%d %d", &n, &q);
for (int i = 0; i < n; i++) {
scanf("%s", buf); string a = buf;
scanf("%s", buf); string b = buf;
if (a == b) continue; // a=b 的对无法贡献答案,且失配位不存在
insert(f(a, b));
}
build();
for (int j = 0; j < q; j++) {
scanf("%s", buf); string t1 = buf;
scanf("%s", buf); string t2 = buf;
if (t1.size() != t2.size()) { printf("0\n"); continue; }
printf("%lld\n", check(f(t1, t2)));
}
return 0;
}
CSP-S 标程 · 复赛真题的其它内容
- 2019 格雷码 · 方案一 递归构造
- 2019 格雷码 · 方案二 异或公式
- 2020 儒略日 · 方案一 逐天模拟
- 2020 儒略日 · 方案二 分段整块跳
- 2021 廊桥分配 · 方案一 枚举分配数模拟
- 2021 廊桥分配 · 方案二 预处理归属加前缀和
- 2022 假期计划 · 方案一 BFS 加平方枚举
- 2022 假期计划 · 方案二 预处理最佳中转
- 2023 密码锁 · 方案一 全域枚举
- 2023 密码锁 · 方案二 基准候选收敛
- 2024 决斗 · 方案一 排序贪心模拟
- 2024 决斗 · 方案二 桶计数线性扫描
- 2025 社团招新 · 方案一 状态计数DP
- 2025 社团招新 · 方案二 超额排序移人
- 2019 括号树 · 方案一 逐点重算
- 2019 括号树 · 方案二 栈加 DFS 递推
- 2020 动物园 · 方案一 子集枚举
- 2020 动物园 · 方案二 位或统计加计数公式
- 2021 括号序列 · 方案一 立方区间 DP
- 2021 括号序列 · 方案二 平方递推
- 2022 策略游戏 · 方案一 暴力扫描
- 2022 策略游戏 · 方案二 ST 表区间极值
- 2023 消消乐 · 方案一 枚举区间加栈
- 2023 消消乐 · 方案二 记忆化递归
- 2024 超速检测 · 方案一 暴力判定
- 2024 超速检测 · 方案二 区间转化加贪心选点
- 2025 道路修复 · 方案一 逐子集重建MST
- 2025 道路修复 · 方案二 预筛MST全局排序
- 2019 树上的数 · 方案一 全排列暴力
- 2019 树上的数 · 方案二 贪心定序加时刻链
- 2020 函数调用 · 方案一 直接模拟
- 2020 函数调用 · 方案二 拓扑序乘子回推
- 2021 回文 · 方案一 环形配对逆向
- 2021 回文 · 方案二 位置表逆向构造
- 2022 星战 · 方案一 重建判定
- 2022 星战 · 方案二 出度计数维护
- 2023 结构体 · 方案一 顺序模拟
- 2023 结构体 · 方案二 统一类型表封装
- 2024 染色 · 方案一 平方 DP
- 2024 染色 · 方案二 last 指针线性 DP
- 2025 谐音替换 · 方案一 逐对逐位置暴力
- 2019 Emiya 家今天的饭 · 方案一 逐列容斥 DP
- 2019 Emiya 家今天的饭 · 方案二 状态折叠
- 2020 贪吃蛇 · 方案一 multiset 模拟
- 2020 贪吃蛇 · 方案二 双端队列停时规律
- 2021 交通规划 · 方案一 Dinic 最小割
- 2021 交通规划 · 方案二 对偶图最短路
- 2022 数据传输 · 方案一 k 为 1 前缀和
- 2022 数据传输 · 方案二 倍增加矩阵
- 2023 种树 · 方案一 按深度贪心
- 2023 种树 · 方案二 堆加合并贪心
- 2024 擂台游戏 · 方案一 逐 K 模拟
- 2024 擂台游戏 · 方案二 倍增分层预处理
- 2025 员工招聘 · 方案一 集合记忆化搜索
- 2025 员工招聘 · 方案二 三维计数DP