洛谷P2292 [HNOI2004]L语言(AC自动机)

题目

字典里给出n(n<=20)个模式串,串长|s|<=10,

m(m<=50)个询问,每次询问一个文本串t(|t|<=2e6)的最长前缀长度,使前缀可以由字典里的串拼接而成

模式串和文本串均由小写字母组成

思路来源

https://www.luogu.com.cn/blog/fusu2333/solution-p2292

题解

很显然的做法是,暴力跳fail,如果fail是一个标记点,代表了一个长度为len的串,

则dp[i]|=dp[i-len],这样做,复杂度是O(m*|fail(s)|*|t|)的,不够优雅

于是注意到|s|只有10,状压10位,预处理fail树,

st[i]表示其fail树的祖先中,是否存在长度1-10的串,存在长度为i的则标记2的i次方这一位

而pre则表示,对于i来说,[i-10,i-1]这10个值中哪些位置dp=1,也用状压表示10位

如果二者有交,则dp[i]=1,这样就降到O(m*|t|)了

代码1(O(1)转移)

#include<bits/stdc++.h>
#define N 205
#define M 2000005
using namespace std;
bool dp[M];
namespace AC{
    int nex[N][26],num[N],fail[N],q[N],h,t,c,id[N],st[N];
    void init(){
        c=0;
        memset(nex[c],0,sizeof nex[c]);
        fail[c]=num[c]=0;
        id[c]=st[c]=0;
    }
    void ins(char *s,int n){
        int rt=0;
        for(int i=0;i<n;i++){
            int v=s[i]-'a';
            if(!nex[rt][v]){
                nex[rt][v]=++c;
                memset(nex[c],0,sizeof nex[c]);
                fail[c]=num[c]=0;
                id[c]=st[c]=0;
            }
            rt=nex[rt][v];
        }
        id[rt]=n;
    }
    void build(){
        h=1;t=0;
        for(int i=0;i<26;i++){
            if(nex[0][i]){
                fail[nex[0][i]]=0,q[++t]=nex[0][i];
            }
        }
        while(h<=t){
            int u=q[h++];
            st[u]=st[fail[u]];
            if(id[u])st[u]+=(1<<id[u]);
            for(int i=0;i<26;i++){
                if(nex[u][i])fail[nex[u][i]]=nex[fail[u]][i],q[++t]=nex[u][i];
                else nex[u][i]=nex[fail[u]][i];
            }
        }
    }
    int query(char *s,int n){
        int rt=0,ans=0,pre;
        dp[0]=1;pre=1;//pre记当前距离为2的0次方 即长度为0
        for(int i=1;i<=n;i++){
            rt=nex[rt][s[i-1]-'a'];
            dp[i]=0;
            if(st[rt]&(pre<<1)){
                dp[i]=1,ans=i;
            }
            pre=(pre<<1)|dp[i];
        }
        return ans;
    }
};
using namespace AC;
int n,m;
char p[M];
int main(){
    scanf("%d%d",&n,&m);
    init();
    for(int i=1;i<=n;i++){
        scanf("%s",p);
        ins(p,strlen(p));
    }
    build();
    for(int j=1;j<=m;++j){
        scanf("%s",p);
        printf("%d\n",query(p,strlen(p)));
    }
    return 0;
}

代码2(朴素回跳)

#include<bits/stdc++.h>
#define N 205
#define M 2000005
using namespace std;
bool dp[M];
namespace AC{
    int nex[N][26],num[N],fail[N],q[N],h,t,c,id[N];
    void init(){
        c=0;
        memset(nex[c],0,sizeof nex[c]);
        fail[c]=num[c]=0;
        id[c]=0;
    }
    void ins(char *s,int n){
        int rt=0;
        for(int i=0;i<n;i++){
            int v=s[i]-'a';
            if(!nex[rt][v]){
                nex[rt][v]=++c;
                memset(nex[c],0,sizeof nex[c]);
                fail[c]=num[c]=0;
                id[c]=0;
            }
            rt=nex[rt][v];
        }
        id[rt]=n;
    }
    void build(){
        h=1;t=0;
        for(int i=0;i<26;i++){
            if(nex[0][i]){
                fail[nex[0][i]]=0,q[++t]=nex[0][i];
            }
        }
        while(h<=t){
            int u=q[h++];
            for(int i=0;i<26;i++){
                if(nex[u][i])fail[nex[u][i]]=nex[fail[u]][i],q[++t]=nex[u][i];
                else nex[u][i]=nex[fail[u]][i];
            }
        }
    }
    int query(char *s,int n){
        int rt=0,ans=0;
        dp[0]=1;
        for(int i=1;i<=n;i++){
            rt=nex[rt][s[i-1]-'a'];
            dp[i]=0;
            for(int j=rt;j;j=fail[j]){
                if(id[j])dp[i]|=dp[i-id[j]];
                if(dp[i])break;
            }
            if(dp[i])ans=i;
        }
        return ans;
    }
};
using namespace AC;
int n,m;
char p[M];
int main(){
    scanf("%d%d",&n,&m);
    init();
    for(int i=1;i<=n;i++){
        scanf("%s",p);
        ins(p,strlen(p));
    }
    build();
    for(int j=1;j<=m;++j){
        scanf("%s",p);
        printf("%d\n",query(p,strlen(p)));
    }
    return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值