• 字符串小结(持续更新)


    只是给忘记模板时的我看的

    AC自动机

    大概流程

    首先对所有模式串建出 T r i e Trie Trie 树,并标记。

    f a i l fail fail 的定义:设 i i i 节点所代表的字符串为 S S S,则 f a i l i fail_i faili 表示 S S S 的所有后缀里面,在 T r i e Trie Trie 树中出现过的最长的那个串所代表的节点。

    然后通过 bfs exttt{bfs} bfs 求出 f a i l fail fail,代码如下:

    void getfail()
    {
    	queue<int>q;
    	for(int i=0;i<26;i++)
    		if(t[0].ch[i])
    			q.push(t[0].ch[i]);
    	while(!q.empty())
    	{
    		int u=q.front();
    		q.pop();
    		for(int i=0;i<26;i++)
    		{
    			if(t[u].ch[i])
    			{
    				t[t[u].ch[i]].fail=t[t[u].fail].ch[i];
    				q.push(t[u].ch[i]);
    			}
    			else t[u].ch[i]=t[t[u].fail].ch[i];//tag1
    		}
    	}
    }
    

    其中为什么 t a g 1 tag_1 tag1 处可以将 u u u 的儿子直接指向 f a i l u fail_u failu 的儿子 v v v

    首先实际的操作应该是新建一个虚拟节点 n e w new new,使 n e w new new u u u 的儿子,且 f a i l n e w = v fail_{new}=v failnew=v

    又由于 n e w new new 本身是新建的节点,没有任何儿子,所以 n e w new new 的儿子全都是要靠新建虚拟节点构成。

    所以 n e w new new 的子树其实和 v v v 的子树是一模一样的。

    那我们不妨用同一棵子树表示他们,也就是说让 u u u 的儿子指向 v v v 而不是新建节点。

    然后由于 n e w new new 树的 f a i l fail fail 全部都是指向 v v v 树的,所以合并到一起不会对 f a i l fail fail 产生影响。

    那么 getfail ⁡ ( ) operatorname{getfail}() getfail() 之后原来的 T r i e Trie Trie 树就会变成一个 DAG 了。

    实际应用

    一、模式串与文本串匹配上的应用

    原理

    首先通过递归 f a i l fail fail,就可以遍历某个串的所有在模式串中出现过的后缀。

    同样,如果建立 f a i l fail fail 树( f a i l i → i fail_i o i failii,就可以通过遍历某一个点 u u u 的子树(设 u u u 所代表的串为 s s s),遍历所有以 s s s 为后缀的串。(也就是往 s s s 的前面加字符)

    其次,对于原 T r i e Trie Trie 树中的某一个节点 u u u(设其代表的串为 s s s),可以遍历统计 u u u 子树内的所有点,遍历所有以 s s s 为前缀的串。(也就是往 u u u 后面加字符)

    那么综合上面两个操作,对于某个串 t t t,我们可以求出所有满足 t t t s s s 的子串的 s s s 串的信息。

    时间复杂度为 O ( n ) O(n) O(n)(遍历一遍 T r i e Trie Trie 树+一遍 f a i l fail fail 树)。

    所以对于解决模式串类的问题,AC 自动机的本质就是对于每一种字符串,除了记录在它后面加字符能到达的出现过的串( T r i e Trie Trie 树),还记录了在它前面加字符能到达的出现过的串( f a i l fail fail 树)。

    那么对于 s s s 串的子串信息,我们可以对 s s s 的前缀跳 f a i l fail fail 链。而对于 t t t 串的扩展串信息( t t t 是某个串的子串),我们可以在 f a i l fail fail 树中遍历 t t t 树的子树,再在 T r i e Trie Trie 树中遍历 遍历到的点 的子树。

    例题

    1.请你分别求出每个模式串 T i T_i Ti 在文本串 S S S 中出现的次数。

    可以直接按我们刚刚的做法来做(跳 S S S 前缀的 f a i l fail fail 链),但是会 T 飞。

    考虑优化,把根到 S S S 路径上的节点都标记(设为 s i z e = 1 size=1 size=1),然后建立 f a i l fail fail 树( f a i l i → i fail_i o i failii),设 s i z e i size_i sizei i i i 这个节点所代表的字符串在 S S S 中出现的次数。

    那么在 f a i l fail fail 树中, i i i 的子树中的所有有效节点都能为 s i z e i size_i sizei 贡献 1 1 1。所以把每一个有效节点 s i z e size size 的初始值都设为 1 1 1 然后在 f a i l fail fail 树上从下往上统计 s i z e size size

    #include<bits/stdc++.h>
    
    #define N 200010
    #define ll long long
    
    using namespace std;
    
    struct Trie
    {
    	int ch[26],fail;
    	ll size;
    }t[N];
    
    int n,node,id[N];
    int cnt,head[N],nxt[N],to[N];
    
    void adde(int u,int v)
    {
    	to[++cnt]=v;
    	nxt[cnt]=head[u];
    	head[u]=cnt;
    }
    
    int insert(string s)
    {
    	int u=0,len=s.size();
    	for(int i=0;i<len;i++)
    	{
    		int v=s[i]-'a';
    		if(!t[u].ch[v]) t[u].ch[v]=++node;
    		u=t[u].ch[v];
    	}
    	return u;
    }
    
    void dfsTrie(string s)
    {
    	int u=0,len=s.size();
    	for(int i=0;i<len;i++)
    	{
    		int v=s[i]-'a';
    		u=t[u].ch[v];
    		t[u].size++;
    	}
    }
    
    void getfail()
    {
    	queue<int>q;
    	for(int i=0;i<26;i++)
    		if(t[0].ch[i])
    			q.push(t[0].ch[i]);
    	while(!q.empty())
    	{
    		int u=q.front();
    		q.pop();
    		for(int i=0;i<26;i++)
    		{
    			if(t[u].ch[i])
    			{
    				t[t[u].ch[i]].fail=t[t[u].fail].ch[i];
    				q.push(t[u].ch[i]);
    			}
    			else t[u].ch[i]=t[t[u].fail].ch[i];
    		}
    	}
    	for(int i=1;i<=node;i++)
    		adde(t[i].fail,i);
    }
    
    void dfsFail(int u)
    {
    	for(int i=head[u];i;i=nxt[i])
    	{
    		int v=to[i];
    		dfsFail(v);
    		t[u].size+=t[v].size;
    	}
    }
    
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    	{
    		string str;
    		cin>>str;
    		id[i]=insert(str);
    	}
    	getfail();
    	string s;
    	cin>>s;
    	dfsTrie(s);
    	dfsFail(0);
    	for(int i=1;i<=n;i++)
    		printf("%lld
    ",t[id[i]].size);
    	return 0;
    }
    /*
    3
    abc
    cde
    de
    abcde
    */
    

    2.https://blog.csdn.net/ez_lcw/article/details/99613063

  • 相关阅读:
    cv2 Qt Platform plugin "cocoa" not found error
    开发scrapy web界面(一)
    java2smali python 粘合脚本
    react如何设置代理
    Nginx启动不了失败原因
    前端,后端,服务器如何部署,转载
    匿名函数普通函数和构造函数
    闭包的认识
    各种命名规范,打好基础才能建设高楼
    mongoose常用操作
  • 原文地址:https://www.cnblogs.com/ez-lcw/p/14448619.html
Copyright © 2020-2023  润新知