当前位置: 首页 > news >正文

AC自动机(拓扑排序优化)

/*
本质:Trie的基础上+KMP的思想(失配指针)
匹配过程中,为了防止失败就前功尽弃,不能利用之前匹配结果的情况,就发明了KMP
如果将KMP运用到高效的Trie上,就可以实现多模式串的匹配
每次匹配失败,就去查找具有最长相同前缀的链
例如,对于abchi和chj这两条链,当我们遍历至i,我们发现父节点的失配指针指向chj中的h,于是我们想知道,chj中h的子节点是否为i,不是,我们跳转过来
继续查找有没有相同的,直至找到或找到空结点
但是,这样的时间复杂度肯定很高,所以我们在处理空结点时,如chj(在代码实现)中h后有一个为空的i节点,那么我们直接i将应该跳转至的节点(即失配指针)
储存下来,这样就可以一步到位(因为是BFS按层序遍历,先处理前面的,正确性可以保证),不用多次跳转了“每个结点的fail指针表示由         根结点到该结点所组成的字符序列的所有后缀        和 整个目标字符串集合(也就是整个Trie树)中的      所有前缀   两者中最长公共的部分。”
*/
#include <iostream>
#include <cstring>
#include <queue>using namespace std;const int N = 2e5+5;
struct node {int son[26];int idx;int deg;//拓扑优化int ans;int fail;void init() {memset(son, 0, sizeof son);deg = idx = 0;}
};
node trie[N];
int pidx, tot;
int ans[N], idx[N];
void insert(string s, int& idx) {int u = 0;for (int i = 0; i < int(s.size()); i++) {int c = s[i] - 'a';if (!trie[u].son[c])trie[u].son[c] = ++tot;u = trie[u].son[c];}// 由于有可能出现相同的模式串,需要将相同的映射到同一个编号if (!trie[u].idx)trie[u].idx = ++pidx;idx = trie[u].idx;
}
void BFS() {/*每个结点fail指向的解决顺序是按照广度优先遍历的顺序完成的,或者说层序遍历的顺序进行的,也就是说我们是在解决当前结点的孩子结点fail的指向时,当前结点的fail指针一定已指向了正确的位置。*/queue<int> q;for (int i = 0; i < 26; i++)if (trie[0].son[i])q.push(trie[0].son[i]);while (!q.empty()) {int u = q.front();q.pop();for (int i = 0; i < 26; i++) {if (trie[u].son[i]) {trie[trie[u].son[i]].fail = trie[trie[u].fail].son[i];//父节点的失配指针已经求出来了,那么子节点的失配指针就是去寻找父节点失配指针,看一下有无对应子节点(为什么只需要找一次,接着看)trie[trie[trie[u].fail].son[i]].deg++;//增加度数q.push(trie[u].son[i]);}elsetrie[u].son[i] = trie[trie[u].fail].son[i];//这就是为什么只用寻找一次的原因,本来子节点的失配指针应该一直跳转着寻找,如果跳转的地方为空,就接着跳,但是通过这个优化,提前处理好,就不需要额外跳了}}/*所有的fail边会构成一棵树,因为fail显然不成环(显然,abcedfg...不可能是a的前缀),而且只会从深处指向浅处(否则前缀必定不同)*//*观察到时间主要浪费在在每次都要跳 fail。如果我们可以预先记录,最后一并求和,那么效率就会优化。于是我们按照 fail 树,做一次内向树上的拓扑排序,就能一次性求出所有模式串的出现次数。build 函数在原先的基础上,增加了入度统计一部分,为拓扑排序做准备。*/
}
void query(string tmp) {int u = 0;for (int i = 0; i < int(tmp.size()); i++) {u = trie[u].son[tmp[i] - 'a'];trie[u].ans++;//统计}
}
void toposort(){//普通的拓扑排序queue<int> q;for (int i = 0; i <= tot; i++)if (!trie[i].deg) q.push(i);while (!q.empty()) {int u = q.front();q.pop();ans[trie[u].idx] = trie[u].ans;int v = trie[u].fail;trie[v].ans += trie[u].ans;if (!--trie[v].deg) q.push(v);}
}int main() {cin.tie(nullptr) -> sync_with_stdio(false);int n;cin >> n;for (int i = 1; i <= n; i++) {string s;cin >> s;insert(s, idx[i]);}BFS();string s;cin >> s;query(s);toposort();for (int i = 1; i <= n; i++) cout << ans[idx[i]] << '\n';
}
http://www.jsqmd.com/news/26830/

相关文章:

  • 详细介绍:Leetcode 3700. Number of ZigZag Arrays II
  • work 3
  • moji 辞书 注音分析
  • 实用指南:OSPF LSA Type 3(Summary LSA)概念及题目
  • .net解决分布式事务简单方案DotNetCore.CAP
  • 《Ai元人文》
  • sklearn 特征选择实战:用 RFE 找到最优特征组合
  • 老旧环境torch版本(0.4.1)环境配置总结
  • ✨《那个让我准时下班的神器,藏在这份编辑器测评里》
  • 代码大全阅读笔记3
  • Newton记录
  • 【备份】不知道什么时候写的IniReader.js
  • CSS尺寸、盒子模型、定位、浮动与布局(Flex/Grid)
  • 通过中国信通院SQL质量管理最高等级评测,天翼云TeleDB引领数据库管理新标准!
  • AtCoder Regular Contest 208 (Div. 2) 题解
  • 第三十篇
  • 代码大阅读笔记
  • 第2次软件基础作业
  • 第二次软件基础作业
  • vs2017安装qt插件及安装qt插件后的设置
  • 实用指南:从0死磕全栈之Next.js Server Actions 入门实战:在服务端安全执行逻辑,告别 API 路由!
  • KeyShot许可管理故障排除步骤
  • 各式各样的Attention - -一叶知秋
  • 重塑生产力:天翼云全球首发RaaS,开启“机器人即服务”商业时代!
  • Python自然语言处理(NLP)入门
  • 【计算机视觉】分水岭搭建医学诊断
  • mysql和java获取经纬度的距离的两种方式
  • Sequence2Sequence - -一叶知秋
  • SQL索引及调优
  • Python列表 _ 创一个购物清单