当前位置:   article > 正文

【董晓算法】竞赛常用知识之字符串2

【董晓算法】竞赛常用知识之字符串2

前言:

本系列是学习了董晓老师所讲的知识点做的笔记

董晓算法的个人空间-董晓算法个人主页-哔哩哔哩视频 (bilibili.com)

 动态规划系列(还没学完)

【董晓算法】动态规划之线性DP问题-CSDN博客

【董晓算法】动态规划之背包DP问题(2024.5.11)-CSDN博客

【董晓算法】动态规划之背包DP与树形DP-CSDN博客

字符串系列

【董晓算法】竞赛常用知识之字符串1-CSDN博客

字典树

作用:

快速插入和查询字符串

插入

儿子数组 ch[p][j] 存储从节点 p沿着j这条边走到的子节点。
边为26个小写字母(a-z)对应的映射值0-25.
每个节点最多可以有26个分叉。例如,ch[0][2]=1,ch[1][0]=2.ch[2][19]=3。

计数数组 cnt[p]存储以节点 p结尾的单词的插入次数

节点编号 idx 用来给节点编号

 1.空 Trie 仅有一个根节点,编号为0。
枚举字符串的每个字符2,从根开始插,

如果有儿子,则p指针走到儿子.
如果没儿子,则 先创建儿子,p指针再走到儿子

3、在单词结束点记录插入次数。

  1. char s[N];
  2. int ch[N][26], cnt[N], idx;
  3. void insert(char* s)
  4. {
  5. int p = 0;
  6. for (int i = 0; s[i]; i++) {
  7. int j = s[i] - 'a';
  8. if (!ch[p][j]) ch[p][j] = ++idx;
  9. p = ch[p][j];
  10. }
  11. cnt[p]++;
  12. }
  13. int query(char* s) {
  14. int p = 0;
  15. for (int i = 0; s[i]; i++) {
  16. int j = s[i] - 'a';
  17. if (!ch[p][j]) return 0;
  18. p = ch[p][j];
  19. }
  20. return cnt[p];
  21. }

查询和插入最主要的就是if (!ch[p][j]) 后不一样,和查询会返回值

最大异或对

任选两个进行异或运算,得到的结果最大是多少

思路:尽可能走相反位,结果最优(从根到叶的每一条路径都表示一个整数)

  1. const int N = 100010;
  2. int n, a[N];
  3. int ch[N * 31][2], idx;//题目是2的23次
  4. void insert(int x) {
  5. int p = 0;
  6. for (int i = 30; i >= 0; i--) {
  7. int j = x >> i & 1; //取出第i位
  8. if (!ch[p][j])ch[p][j] = ++idx;
  9. p = ch[p][j];
  10. }
  11. }
  12. int query(int x) {
  13. int p = 0, res = 0;
  14. for (int i = 30; i >= 0; i--) {
  15. int j = x >> i & 1; //取出第i位
  16. if (ch[p][!j]) {
  17. res += 1 << i; //累加位权
  18. p = ch[p][!j];
  19. }
  20. else p = ch[p][j];
  21. }
  22. return res;
  23. }
  24. int main() {
  25. cin >> n;
  26. for (int i = 1; i <= n; i++)
  27. cin >> a[i], insert(a[i]);
  28. int ans = 0;
  29. for (int i = 1; i <= n; i++)
  30. ans = max(ans, query(a[i]));
  31. cout << ans;
  32. return 0;
  33. }


int query(int x) {
    int p = 0, res = 0;
    for (int i = 30; i >= 0; i--) {
        int j = x >> i & 1; //取出第i位
        if (ch[p][!j]) {
            res += 1 << i; //累加位权
            p = ch[p][!j];
        }
        else p = ch[p][j];
    }
    return res;
}

AC自动机 

AC 自动机(简单版) - 洛谷 (luogu.com.cn)

AC自动机是多模式匹配算法。给定 n个模式串和一个主串,查找有多少个模式串在主串中出现过 

步骤

1.构造 Trie 树
先用n个模式串构造一颗Trie 。
Trie 中的一个节点表示一个从根到当前节点的字符串。
根节点表示空串,节点(5表示“s”,节点6表示“sh",节点7表示“she”。
如果节点是个模式串,则打个标记。例如,cnt[7]=1。
2.构造 AC自动机在 Trie 上构建两类边:回跳边和转移边

3.扫描主串匹配

回跳边指向父节点的回跳边所指节点的儿子,从一个节点指向其最长后缀匹配节点

转移边指向当前节点的回跳边所指节点的儿子,从一个节点指向其直接子节点的链接 

 

构树代码就是上面字典树的代码 

构造 AC自动机

  1. void build() {//建AC自动机
  2. queue<int> q;
  3. for (int i = 0; i < 26; i++)
  4. if (ch[0][i])q.push(ch[0][i]);
  5. while (q.size()) {
  6. int u = q.front(); q.pop();
  7. for (int i = 0; i < 26; i++) {
  8. int v = ch[u][i];
  9. if (v)ne[v] = ch[ne[u]][i], q.push(v);
  10. else ch[u][i] = ch[ne[u]][i];
  11. }
  12. }
  13. }

 查找单词出现次数

 扫描主串,依次取出字符 s[k].
1.i指针走主串对应的节点,沿着树边或转移边走保证不回退。
2.j指针沿着回跳边搜索模式串,每次从当前节点走到根节点,把当前节点中的所有后缀模式串一网打尽,保证不漏解。
3.扫描完主串,返回答案。

  1. int query(char *s){
  2. int ans=0;
  3. for(int k=0,i=0;s[k];k++){
  4. i=ch[i][s[k]-'a'];
  5. for(int j=i;j&&~cnt[j];j=ne[j])//~cnt[i]检查cnt是不是-1
  6. ans+=cnt[j], cnt[j]=-1;
  7. }
  8. return ans;
  9. }

KMP和AC自动机对比

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/598508
推荐阅读
相关标签
  

闽ICP备14008679号