训练总结 1.29

  今天看AC自动机,看得迷迷糊糊,看起来就是字典树+KMP。字典树还好说,当时KMP有点没看懂,又看了一下,这次KMP倒是懂了,AC自动机还是有点没看懂。

   照着博客上的模板试着敲了一遍。构造next数组那里还是有点看不懂。还得继续看。

#include<bits/stdc++.h>
using namespace std;

const int maxnode=11000;
const int sigma_size=26;

struct AC_Auto
{
    int ch[maxnode][sigma_size];
    int val[maxnode];//字符串的节点字符非0;
    int next[maxnode];//小串的前i各元素最长公共前缀和后缀的长度
    int last[maxnode];//last[i]==j  j节点表示的是i节点单词的后缀,且j节点是单词末字母节点
    int sz;

    void Clear()//初始化0节点
    {
        sz=1;
        memset(ch[0],0,sizeof(ch[0]));
        val[0]=0;
    }

    int idx(char ch)
    {
        return ch-'a';
    }

    void Insert(char *s,int v)//v非0表示一个单词末尾
    {
        int u=0,n=strlen(s);
        for(int i=0;i<n;i++)
        {
            int id=idx(s[i]);
            if(ch[u][id]==0)
            {
                ch[u][id]=sz;
                memset(ch[sz],0,sizeof(ch[sz]));
                val[sz]=0;
                sz++;
            }
            u=ch[i][id];
        }
        val[u]=v;
    }

    void getNext()//构造next数组和last数组
    {
        queue<int>q;
        last[0]=next[0]=0;
        for(int i=0;i<sigma_size;i++)
        {
            int u=ch[0][i];
            if(u)//存在以i开头的单词,u为节点编号
            {
                next[u]=last[u]=0;
                q.push(u);
            }
        }

        while(!q.empty())//按bfs顺序计算next数组的值
        {
            int now=q.front();
            q.pop();
            for(int i=0;i<sigma_size;i++)
            {
                int u=ch[r][i];//检索下一层
                if(u==0) continue;
                q.push(u);

                int v=next[now];
                while(v&&ch[v][i]==0)
                    v=next[v];
                next[u]=ch[v][i];
                last[u]=val[next[u]]!=0?next[u]:last[next[u]];
            }
        }
    }

    void print(int i)//打印与节点i后缀相同的前缀节点编号,先保证val[i]>0
    {
        if(i)
        {
            printf("%d\n",i);
            print(last[i]);
        }
    }

    void Find(char *s)
    {
        int j=0,n=strlen(s);
        for(int i=0;i<n;i++)
        {
            int id=idx(s[i]);
            while(j&&ch[j][id]==0)
                j=next[j];
            j=ch[j][id];
            if(val[j])
                print(j);
            else if(last[j]!=0)
                print(last[j]);
        }
    }

};

   问了一下组长,他看的是那种动态指针的

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
//:给你一个文本和多个单词,问你出现了多少个单词。注意单词可能会重复。
const int MAXN = 1000001; //模式串的最大长度MAXN - 1
const int MAXM = 51; //单词最大长度为MAXM - 1
const int KEYSIZE = 26; //26个小写字母
struct Node {
      Node *fail;  //失配指针 指向和当前结点的某个点的后缀匹配的最长前缀的位置
      Node *next[KEYSIZE]; //儿子结点个数 子节点指针
      int count; //单词个数
      Node() {
            fail = NULL;
            count = 0;
            memset(next, 0, sizeof(next));
      }
     ~Node() {
          delete next;
      }
}*q[MAXN/2];
void insert(char *str, Node *root)//模式串插入   构建字典树
{
      Node *p = root;
      int i = 0;
      while(str[i]){
                //获取字母对应id
           int index = str[i] - 'a';
           //检查子节点是否存在,不存在则创建新的子结点
           if(p -> next[index] == NULL)
                  p -> next[index] = new Node();
           p = p -> next[index];
           i ++;
      }
      p -> count ++; //在单词的最后一个结点count + 1,代表一个单词
}
void build_ac_automation(Node *root)//构建fail指针 基于bfs实现
{
      root -> fail = NULL;
      //根结点的失配指针,直接指向NULL
      //对于根结点下所有的子节点(与根结点直接相连的结点的fail肯定直接指向root)
      //失配指针一定是指向root的(当一个字符都不能匹配的时候,自然不存在更短的能够与之匹配的前缀了)
      int head, tail;
      head = tail = 0;
      q[tail ++] = root;
      //逐层计算每一层的结点的fail指针
      //当每个结点计算完毕,保证它所有的后继都计算出来
      //每次弹出一个结点temp,询问它的每个字符对应的子结点
      //第i个子结点记为 temp->next[i]

      while(head < tail) {
            Node *temp = q[head ++];
            for(int i = 0; i < KEYSIZE; i ++) {
                        //如果temp->next[i]==NULL
                        //那么temp->next[i]指向temp的失配指针的i号子结点
                        //temp->next[i]=temp->fail->next[i]
                        //如果temp->next[i]不等于NULL
                        //需要构造temp->next[i]的失配指针
                        //temp[i]->next[i]->fail=temp[i]->fail->next[i]
                if(temp -> next[i] != NULL) {
                     if(temp == root){
                          temp -> next[i] -> fail = root;
                     }else {
                          Node *p = temp -> fail;
                          while(p != NULL) {
                               if(p -> next[i] != NULL) {
                                         temp -> next[i] -> fail = p -> next[i];
                                    break;
                               }
                               p = p -> fail;
                          }
                          if(p == NULL)
                               temp -> next[i] -> fail = root;
                     }
                     q[tail ++] = temp -> next[i];
                }
           }
      }
}
//利用fail指针进行匹配
int query(char *str, Node *root)//目标串匹配
{
      int i = 0, cnt = 0;
      Node *p = root;
      while(str[i]) {
           int index = str[i] - 'a';
           //根据目标串给定的字符进行遍历
           //每次检查当前结点是否为结尾结点
           //还需要检查失配指针指向的结点
           while(p -> next[index] == NULL && p != root) p = p -> fail;
           p = p -> next[index];
           //当前单词从来没有出现过的话直接回到匹配之初
           p = (p == NULL) ? root : p;
           Node *temp = p;
           //找到一个子串以temp结点结尾
           //可以统计子串数目或者是输出子串的位置
           //累加所有的count即为模式串的个数
           while(temp != root && temp -> count != -1) {
                 cnt += temp -> count;
                 temp -> count = -1;
                 temp = temp -> fail;
           }
           i ++;
      }
      return cnt;
}
int main()
{
      int n, cas;
      Node *root;
      char keyword[MAXM]; //单词
      char str[MAXN]; //模式串
      scanf("%d", &cas);//case
      while(cas --) {
            scanf("%d", &n);
            getchar();
            root = new Node();
            while(n --) {
                 scanf("%s", keyword);
                 insert(keyword, root);
            }
            build_ac_automation(root);
            scanf("%s", str);
            printf("%d\n", query(str, root));
      }
      return(0);
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值