• AC自动机 hdu 2222


    。。完全学习两位牛人的。。有链接 自己转过去看吧

    //AC自动机 HDU 2222  参考
    //http://www.cppblog.com/mythit/archive/2009/07/30/80633.html
    //http://www.cnblogs.com/destinydesigner/archive/2009/10/15/1584191.html
    #include <iostream>
    using namespace std;
    
    
    const int kind = 26; 
    struct node{  
    	node *fail;       //失败指针
    	node *next[kind]; //Tire每个节点的个子节点(最多个字母)
    	int count;        //是否为该单词的最后一个节点
    	node(){           //构造函数初始化
    		fail=NULL; 
    		count=0; 
    		memset(next,NULL,sizeof(next)); 
    	} 
    }*q[500001];          //队列,方便用于bfs构造失败指针
    char keyword[51];     //输入的单词
    char str[1000001];    //模式串
    int head,tail;        //队列的头尾指针
    
    
    
    void insert(char *str,node *root)
    { 
    	int i=0,index;  
    	node *p=root; 
    	while(str[i])
    	{ 
    		index=str[i]-'a'; 
    		if(p->next[index]==NULL) 
    			p->next[index]=new node();  
    		p=p->next[index];
    		i++;
    	} 
    	p->count++;     //在单词的最后一个节点count+1,代表一个单词
    }
    
    /*
    假设有一个节点k,他的失败指针指向j。那么k,j满足这个性质:
    设root到j的距离为n,则从k之上的第n个节点到k所组成的长度为n的单词,与从root到j所组成的单词相同。
    
    那么我们要怎样构建这个东西呢?其实我们可以用一个简单的BFS搞定这一切。
    对于每个节点,我们可以这样处理:
    设这个节点上的字母为C,沿着他父亲的失败指针走,直到走到一个节点,他的儿子中也有字母为C的节点。
    然后把当前节点的失败指针指向那个字母也为C的儿子。如果一直走到了root都没找到,那就把失败指针指向root
    
    最开始,我们把root加入队列(root的失败指针显然指向自己),这以后我们每处理一个点,就把它的所有儿子加入队列,直到搞完。
    */
    void build_ac_automation(node *root){
    	int i;
    	root->fail = NULL; 
    	q[head++] = root; 
    	while(head != tail)
    	{ 
    		node *temp = q[tail++]; 
    		node *p = NULL; 
    		for(i=0; i < 26; i++)
    		{ 
    			if(temp->next[i] != NULL)
    			{ 
    				if(temp==root) 
    					temp->next[i]->fail = root;                 
    				else
    				{ 
    					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[head++]=temp->next[i];//头指针后移
    			} 
    		}   
    	} 
    }
    
    
    
    
    /*
    匹配过程分两种情况:
    (1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,
    此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;
    (2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,
    匹配过程随着指针指向root结束。
    重复这2个过程中的任意一个,直到模式串走到结尾为止。
    */
    /*
    对于Trie树中的一个节点,对应一个序列s[1...m]。此时,p指向字符s[m]。
    若在下一个字符处失配,即p->next[s[m+1]] == NULL,则由失配指针跳到另一个节点(p->fail)处,该节点对应的序列为s[i...m]。
    若继续失配,则序列依次跳转直到序列为空或出现匹配。
    在此过程中,p的值一直在变化,但是p对应节点的字符没有发生变化。
    在此过程中,我们观察可知,最终求得得序列s则为最长公共后缀。
    另外,由于这个序列是从root开始到某一节点,则说明这个序列有可能是某些序列的前缀。
    再次讨论p指针转移的意义。
    如果p指针在某一字符s[m+1]处失配(即p->next[s[m+1]] == NULL),则说明没有单词s[1...m+1]存在。
    此时,如果p的失配指针指向root,则说明当前序列的任意后缀不会是某个单词的前缀。
    如果p的失配指针不指向root,则说明序列s[i...m]是某一单词的前缀,于是跳转到p的失配指针,以s[i...m]为前缀继续匹配s[m+1]。
    对于已经得到的序列s[1...m],由于s[i...m]可能是某单词的后缀,s[1...j]可能是某单词的前缀,所以s[1...m]中可能会出现单词。
    此时,p指向已匹配的字符,不能动。于是,令temp = p,然后依次测试s[1...m], s[i...m]是否是单词。
    */
    int query(node *root){ 
    	int i=0,cnt=0,index,len=strlen(str); 
    	node *p=root;  
    	while(str[i]){  
    		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; 
    		while(temp!=root){ //统计其中的单词
    			cnt+=temp->count; 
    			temp->count=0; 
    			temp=temp->fail; 
    		} 
    		i++;                 
    	}    
    	return cnt; 
    }
    
    
    
    int main(){ 
    	int n,t; 
    	scanf("%d",&t); 
    	while(t--){  
    		head=tail=0; 
    		node *root=new node(); 
    		scanf("%d",&n); 
    		getchar(); 
    		while(n--){ 
    			gets(keyword); 
    			insert(keyword,root); 
    		} 
    		build_ac_automation(root); 
    		scanf("%s",str); 
    		printf("%d\n",query(root));  
    	} 
    	return 0; 
    }
    

  • 相关阅读:
    Vue基础
    Document
    Document
    Document
    Document
    Document
    Document
    Document
    Document
    Document
  • 原文地址:https://www.cnblogs.com/steady/p/1950376.html
Copyright © 2020-2023  润新知