zoukankan      html  css  js  c++  java
  • 自动机(hdu2222模板)

    给定n个长度不超过50的由小写英文字母组成的单词准备查询,以及一篇长为m的文章,问:文中出现了多少个待查询的单词。多组数据。

    输入格式
    第一行一个整数t,表示数据组数;
    对于每组数据,第一行一个整数n,接下去n行表示n个单词,最后一行输入一个字符串,表示文章。

    输出格式
    对于每组数据,输出一个数,表示文中出现了多少个待查询的单词。

    样例
    样例输入
    1
    5
    she
    he
    say
    shr
    her
    yasherhs
    样例输出
    3

    以下代码和思路是菜鸡刚学ac自动机时学的...以后有所进步的话随时会更改0.0

    #include <string.h>
    #include <stdio.h>
    #include <algorithm>
    #include <string>
    #include <queue>
    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=2*1e6+10;
    int tree[maxn][26],word[maxn],fail[maxn];
    int tot;
    void insert(string s)
    {
    	int u=0,i;
    	for (i=0; i<s.size(); i++)
    	{
    		int c=s[i]-'a';
    		if (!tree[u][c])
    		{
    			tree[u][c]=++tot;
    			memset(tree[tot],0,sizeof(tree[tot]));
    			//清空tot行的元素
    		}
    		u=tree[u][c];
    	}
    	word[u]++;
    }
    void bfs()
    {
    	queue<int>q;
    	int i,j;
    	for (i=0; i<26; i++)//26个单词依次遍历
    	{
    		if (tree[0][i])
    		{
    			fail[tree[0][i]]=0;
    			q.push(tree[0][i]);
    		}
    	}
    //fail[now]->当前节点now的失败指针指向的地方
    //tire[now][i]->下一个字母为i+'a'的节点的下标为tire[now][i]	 
    	while(!q.empty())
    	{
    		int u=q.front();
    		q.pop();
    		for (i=0; i<26; i++)
    		{
    			if (tree[u][i])//tree[u][i]是当前节点,u是他的父节点
    			{
    //如果有这个子节点为字母i+'a',则
    //让这个节点的失败指针指向(((他父亲节点)
    //的失败指针所指向的那个节点)的下一个节点)
    				fail[tree[u][i]]=tree[fail[u]][i];
    				q.push(tree[u][i]);
    			}
    			else
    //否则就让当前节点的这个子节点
    //指向当前节点fail指针的这个子节点
    				tree[u][i]=tree[fail[u]][i];
    		}
    	}
    }
    int query(string s)
    {
    	int i,j,u=0,ans=0;
    	for (i=0; i<s.size(); i++)
    	{
    		int c=s[i]-'a';
    		u=tree[u][c];
    		for (j=u; j&&word[j]!=-1; j=fail[j])
    		{
    //一直向下寻找,直到匹配失败(失败指针指向根或者当前节点已找过).
    			ans+=word[j];
    			word[j]=-1;
    		}
    	}
    	return ans;
    }
    int main()
    {
    	int i,j,n,t;
    	scanf("%d",&t);
    	while(t--)
    	{
    		tot=0;
    		memset(word,0,sizeof(word));
    		for (i=0; i<26; i++)
    			tree[0][i]=0;
    		scanf("%d",&n);
    		string s;
    		while(n--)
    		{
    			//scanf("%s",s);
    			cin>>s;
    			insert(s);
    		}
    		fail[0]=0;
    		bfs();
    		//scanf("%s",s);
    		cin>>s;
    		printf("%d
    ",query(s));
    	}
    	return 0;
    }
    

    更新一下基本思路吧
    总共三个主要函数,开始一个是insert,插入多个字符串,目的是构建一棵字典树
    然后是构建fail数组,使用bfs,和kmp的next数组有点类似。
    最后是query函数,输入待查询的字符串,利用tree[][]数组和fail[]数组以及for(j=u; j&&word[j]!=-1; j=fail[j]){ans+=word[j];word[j]=-1;}
    进行查询。

    1.字典树,构建tir树:
    字典树的构建过程是这样的,当要插入许多单词的时候,我们要从前往后遍历整个字符串,
    当我们发现当前要插入的字符其节点再先前已经建成,我们直接去考虑下一个字符即可,
    当我们发现当前要插入的字符没有
    再其前一个字符所形成的树下没有自己的节点,我们就要创建一个新节点来表示这个字符,
    接下往下遍历其他的字符。然后重复上述操作。

    2.找Fail指针,解决失配问题
    当发现失配的字符失配的时候,跳转到fail指针指向的位置,
    然后再次进行匹配操作,AC自动机之所以能实现多模式匹配,就归功于Fail指针的建立。
    当前节点t有fail指针,其fail指针所指向的节点和t所代表的字符是相同的。
    因为t匹配成功后,我们需要去匹配t->child,发现失配,那么就从t->fail这个节点开始再次去进行匹配。

    求法:
    Fail指针的求法:
    Fail指针用BFS来求得,对于直接与根节点相连的节点来说,如果这些节点失配,
    他们的Fail指针直接指向root即可,
    其他节点其Fail指针求法如下:
    假设当前节点为father,其孩子节点记为child。求child的Fail指针时,
    首先我们要找到其father的Fail指针所指向的节点,假如是t的话,
    我们就要看t的孩子中有没有和child节点所表示的字母相同的节点,如果有的话,
    这个节点就是child的fail指针,如果发现没有,则需要找father->fail->fail这个节点,
    然后重复上面过程,如果一直找都找不到,则child的Fail指针就要指向root。

    3.最后一步,文本串匹配
    1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,
    如果当前匹配的字符是一个单词的结尾,我们可以沿着当前字符的fail指针,
    一直遍历到根,如果这些节点末尾有标记(此处标记代表,节点是一个单词末尾的标记),
    这些节点全都是可以匹配上的节点。
    我们统计完毕后,并将那些节点标记。此时只需沿该路径走向下一个节点继续匹配即可,
    目标字符串指针移向下个字符继续匹配;
    2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。
    重复这2个过程中的任意一个,直到模式串走到结尾为止。

  • 相关阅读:
    性能测试二十四:环境部署之Redis多实例部署
    性能测试二十三:环境部署之Redis安装和配置
    性能测试二十二:环境部署之Nginx
    性能测试二十一:环境部署之mysql
    性能测试二十:环境部署之Tomcat多实例部署+日志监控
    性能测试十九:jmeter参数优化+排错
    Zookeeper学习
    Zookeeper学习
    Zookeeper学习
    Hadoop Balancer源码解读
  • 原文地址:https://www.cnblogs.com/shidianshixuan/p/13974675.html
Copyright © 2011-2022 走看看