[AC自动机]luoguP3966

模板题和一些坑点:
1.输入的单词可能会有重复的,重复的单词不用重复计算,将相同单词的最小id保存下来就行
2.朴素的AC自动机算法最后一个点会T,用last优化,直接跳到单词末节点,效率提高十分显著。
代码如下:

#include<bits/stdc++.h>//lst优化 
#define lowbit(x) ((x)&(-(x)))
#define ll long long
#define INF 0x3f3f3f3f
#define CLR(a) memset(a, 0, sizeof(a))
using namespace std;
const int maxn=1e6+300;
int vis[205],n,mp[205];
struct Trie{
    int nxt[maxn][27],fail[maxn],end[maxn],lst[maxn]; 
    int cnt; 
    void insert(char buf[],int id){
        int len=strlen(buf);
        int now=0;
        for ( int i=0;i<len;i++ ){
            int x=buf[i]-'a';
            if (!nxt[now][x]) nxt[now][x]=++cnt;
            now=nxt[now][x];
        }
        if(!end[now]) end[now]=id;
        mp[id]=end[now];mp[id]记录的是与第k号单词相同的单词的最小编号,这样可以解决重复单词
    }
    void build(){
        queue<int>q;
        for (int i=0;i<26;i++) if(nxt[0][i]) q.push(nxt[0][i]);
        while (!q.empty()){
            int now=q.front();
            q.pop();
            for ( int i=0;i<26;i++ ){
            	int x=nxt[now][i];
                if (!x) 
                    nxt[now][i]=nxt[fail[now]][i];
                else{ 
                    fail[x]=nxt[fail[now]][i];
                    lst[x]=end[fail[x]]?fail[x]:lst[fail[x]];//last优化
                    q.push(x);
                }
            }
        }
    }
    void query(char buf[]){ 
        int len=strlen(buf);
        int now=0;
        int res=0;
        for ( int i=0;i<len;i++ ) { 
        	if(buf[i]=='#'){
        		now=0;continue;
			}
            now=nxt[now][buf[i]-'a'];
            if(end[now]) vis[end[now]]++;
            int tmp=lst[now];
            while ( tmp ){
               	vis[end[tmp]]++;
                tmp=lst[tmp];
            }
        }
        for(int i=1;i<=n;i++) printf("%d\n",vis[mp[i]]);
    }
}ac;
char buf[maxn],s[maxn];
int main() {
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		CLR(s);
		scanf("%s",s);
		ac.insert(s,i);
		int len=strlen(s);
		s[len]='#';
		strcat(buf,s);
	} 
	ac.build();
	ac.query(buf);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值