AC 自动机

『我从来没有会过任何串串科技。』

AC 自动机

相当于多个 KMP。也就是多个模板字符串上搞某种匹配问题。

建 AC 自动机

假设我有 \(n\) 个字符串为 \(abc,bcd,bd,c\)\(n=4\))。

首先我们对 \(n\) 个字符串建立 Trie 树。长这样:【图】。

如果我们有个匹配串为 \(abcdbc\),那么在这棵 Trie 树上暴力匹配会似。但是当我们匹配完 \(abc\) 这个串之后直接跳到 \(bcd\) 对应的 \(c\),就可以大大节省时间复杂度。那么这里称一个节点 \(x\) 失配之后跳到的节点 \(y\)\(x\) 的失配指针对应的点。在例子中 \(abc\)\(c\) 对应节点的失配指针所对的节点就是 \(bcd\)\(c\)

对于失配指针,有个性质:如果 \(x\) 的失配指针指向 \(y\),那么 \(root \to y\) 形成的字符串应为 \(root \to x\) 形成的字符串的后缀。那么我们就可以找失配指针了,失配指针应该是在 Trie 上 \(root \to y\)\(root \to x\) 后缀的 \(y\) 中,最大的 \(dep_y\) 对应的点。

现在我们来求失配指针。首先,第一层的点的失配指针为 \(root\),第 \(i\) 层的失配指针的 \(dep\) 一定小于 \(i\)。那么求 \(x\) 的失配指针的时候只需要看 \(x\) 的父亲的失配指针对应的点 \(y\) 的那个和 \(x\) 相同值的点了(1)。如果不存在相同值的点,那就不存在了,因为如果存在,那么它的父亲一定会有这个出边,又因为 Trie 上不存在两个不同节点 \(root\) 到它形成的字符串相同,所以不可能。如果节点 \(x\) 没有值为 \(y\) 的儿子,那么就让它的儿子变成 \(x\) 的失配指针的那个儿子(2)。

哦,我是不是说错了。我们的(1)是没有正确性的,但是如果我们加上(2)之后就有了,那个证明是唐的,删了。自行理解。

建 AC 自动机的时候可以按照深度 BFS,这样对于每个 \(x\),我们都一定知道它父亲的失配指针了。为了方便,我们建一个虚点 \(0\),让 \(0\) 的所有出边指向 \(root\) 节点 \(1\),然后 \(1\) 的失配指针指向虚点。

il void build(){//建 AC 自动机
	queue<int> qu;qu.push(1),tr[1].fail=0;
	for(re int i=0;i<26;++i) tr[0].ch[i]=1;
	while(!qu.empty()){
		int u=qu.front();qu.pop();
		for(re int i=0;i<26;++i){
			int v=tr[u].ch[i];
			int fail=tr[u].fail;
			if(!v) tr[u].ch[i]=tr[fail].ch[i];
			else tr[v].fail=tr[fail].ch[i],qu.push(v);
		}
	}
	return ;
} 

不难观察到,我们 AC 自动机相当于弄了两个东西出来,一个是 fail 树,一个是 DAG。fail 树保证了每个节点的父亲在 Trie 上一定是它的后缀,那么节点 \(x\) 的祖先也都是 \(x\) 在 Trie 上的后缀了。然后建出来的 DAG 貌似没什么用。

例题

P5357 【模板】AC 自动机

我们把 fail 树建出来,那么一个节点 \(x\) 的出现次数就应该是它的子树 \(siz\) 和。因为它是任意一个后代的前缀。而我们把一个匹配串拿去匹配的时候,是将它的每个前缀对应 Trie 上的最长后缀的那个节点记录次数,所以算出来是对的。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define re register
#define il inline
#define pii pair<int,int>
#define x first
#define y second
#define gc getchar()
#define rd read()
#define debug() puts("------------")

namespace yzqwq{
    il int read(){
        int x=0,f=1;char ch=gc;
        while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc;}
        while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=gc;
        return x*f;
    }
    il int qmi(int a,int b,int p){
        int ans=1;
        while(b){
            if(b&1) ans=ans*a%p;
            a=a*a%p,b>>=1;
        }
        return ans;
    }
    il int gcd(int a,int b){
        if(!b) return a;
        return gcd(b,a%b);
    }
    il int lcm(int a,int b){
        return a/gcd(a,b)*b;
    }
    il void exgcd(int a,int b,int &x,int &y){
        if(!b) return x=1,y=0,void(0);
        exgcd(b,a%b,x,y);
        int t=x;
        x=y,y=t-a/b*x;
        return ;
    }
    il int F(int n,int a,int b,int c){
        //sum{|_ (ai+b)/c _| i:0~n}
        if(!n) return b/c;
        if(!a) return (n+1)*(b/c);
        if(a>=c||b>=c){
            int x=a/b,y=b/c;
            return n*(n+1)/2*x+(n+1)*y+F(n,a%c,b%c,c);
        }
        int m=(a*n+b)/c;
        return n*m+F(m-1,c,c-b+1,a);
    }
    struct lb{
        int d[64];
        il void print(){
            for(re int i=0;i<63;++i)
            if(d[i]) printf("%lld:%lld\n",i,d[i]);
            return ;
        }
        lb(){memset(d,0,sizeof(d));}
        il void operator +=(int x){
            for(re int i=62;i>=0;--i){
                if(!(x&(1ll<<i))) continue;
                if(d[i]) x^=d[i];
                else return d[i]=x,void(0);
            }
            return ;
        }
        int& operator [](int x){
            return d[x];
        }
        il void operator +=(lb &x){
            for(re int i=62;i>=0;--i)
            if(x[i]) *this+=x[i];
            return ;
        }
        il friend lb operator +(lb &x,lb &y){
            lb z=x;
            for(re int i=62;i>=0;--i)
            if(y[i]) z+=y[i];
            return z;
        }
        il int Max_calc(){
            int ans=0;
            for(re int i=62;i>=0;--i)
            if((ans^d[i])>ans) ans^=d[i];
            return ans;
        }
    };
	mt19937 rnd(time(0));
}
using namespace yzqwq;

const int N=2e5+10;
struct Trie{
	int ch[26],fail;
}tr[N];
int idx=1,id[N];
int siz[N];
vector<int> e[N];

il void insert(string s,int k){
	int u=1;
	for(re int i=0;i<s.size();++i){
		if(!tr[u].ch[s[i]-'a']) tr[u].ch[s[i]-'a']=++idx;
		u=tr[u].ch[s[i]-'a'];
	}
	id[k]=u;
	return ;
}
il void build(){
	queue<int> qu;qu.push(1),tr[1].fail=0;
	for(re int i=0;i<26;++i) tr[0].ch[i]=1;
	while(!qu.empty()){
		int u=qu.front();qu.pop();
		for(re int i=0;i<26;++i){
			int v=tr[u].ch[i];
			int fail=tr[u].fail;
			if(!v) tr[u].ch[i]=tr[fail].ch[i];
			else tr[v].fail=tr[fail].ch[i],qu.push(v);
		}
	}
	return ;
} 
il void match(string s){
	int u=1;
	for(re int i=0;i<s.size();++i){
		u=tr[u].ch[s[i]-'a'];
		++siz[u];
	}
	return ;
}
il void dfs(int u){
	for(auto v:e[u]) dfs(v),siz[u]+=siz[v];
	return ;
}

il void solve(){
	int n=rd;
	for(re int i=1;i<=n;++i){
		string s;cin>>s;
		insert(s,i);
	}
	build();
	for(re int i=1;i<=idx;++i) e[tr[i].fail].push_back(i);
	string t;cin>>t;
	match(t),dfs(0);
	for(re int i=1;i<=n;++i) printf("%lld\n",siz[id[i]]);
    return ;
}

signed main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
    int t=1;while(t--)
    solve();
    return 0;
}

P3966 [TJOI2013] 单词

相当于多个匹配串,match 多遍即可。

UVA11019 Matrix Matcher

AC 自动机的做法貌似很难。大概是先对行跑 AC 自动机,然后对列就可以看成一个数组,再跑 KMP。哈希的话是二维哈希模板。

来源链接:https://www.cnblogs.com/harmisyz/p/18802605

请登录后发表评论

    没有回复内容