[BZOJ 3413] 匹配

本文介绍了一种使用后缀自动机和线段树解决字符串匹配问题的高级算法。首先通过构建后缀自动机来快速查找子串在主串中的首次出现位置,然后利用线段树优化计算子串在主串中的出现次数,从而得到精确的匹配结果。该算法适用于处理大规模数据集,时间复杂度为O(∑|T|logn)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、题目

点此看题

二、解法

先对 S S S建出后缀自动机,然后先判断 T T T S S S中是否出现过,对于自动机每个点我们维护一个最小的 e n d p o s endpos endpos,就可以知道 T T T S S S第一次出现的位置了。

我们可以先对答案赋一个初始值,根据 T T T S S S中是否出现过而定,如果出现过,设出现位置( e n d p o s endpos endpos)为 F F F,那么答案的初始值应该是 F − ∣ T ∣ F-|T| FT。否则应该是 n n n,就考虑失配拿一下的答案。

然后我们在此把 T T T放在 S S S上匹配,设匹配到了一个长度 i i i,我们要看现在匹配的部分的出现次数,把它计入答案中,也就是当前点 p p p e n d p o s endpos endpos [ 1 , F − ∣ T ∣ + i ] [1,F-|T|+i] [1,FT+i]中出现个数,用线段树合并来维护即可。

时间复杂度 O ( ∑ ∣ T ∣ log ⁡ n ) O(\sum|T|\log n) O(Tlogn),贴个代码 q w q qwq qwq

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 200005;
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,m,k,tot,f[M],cnt,last,len[M],fa[M],ch[M][10];
int Index,mi[M],rt[M],ls[20*M],rs[20*M],sum[20*M];char s[M];
struct edge
{
    int v,next;
    edge(int V=0,int N=0) : v(V) , next(N) {}
} e[2*M];
void ins(int &x,int l,int r,int id)
{
    x=++Index;
    sum[x]=1;
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(mid>=id) ins(ls[x],l,mid,id);
    else ins(rs[x],mid+1,r,id);
}
void add(int c)
{
    int p=last,np=last=++cnt;
    ins(rt[np],1,n,mi[np]=len[np]=len[p]+1);
    for(; p && !ch[p][c]; p=fa[p]) ch[p][c]=np;
    if(!p) fa[np]=1;
    else
    {
        int q=ch[p][c];
        if(len[q]==len[p]+1) fa[np]=q;
        else
        {
            int nq=++cnt;
            fa[nq]=fa[q];
            memcpy(ch[nq],ch[q],sizeof ch[q]);
            len[nq]=len[p]+1;
            fa[q]=fa[np]=nq;
            for(; p && ch[p][c]==q; p=fa[p]) ch[p][c]=nq;
        }
    }
}
int merge(int x,int y)
{
    if(!x || !y) return x|y;
    int p=++Index;
    sum[p]=sum[x]+sum[y];
    ls[p]=merge(ls[x],ls[y]);
    rs[p]=merge(rs[x],rs[y]);
    return p;
}
int ask(int i,int l,int r,int L,int R)
{
    if(!i || L>r || l>R || L>R) return 0;
    if(L<=l && r<=R) return sum[i];
    int mid=(l+r)>>1;
    return ask(ls[i],l,mid,L,R)+ask(rs[i],mid+1,r,L,R);
}
void dfs(int u)
{
    for(int i=f[u]; i; i=e[i].next)
    {
        int v=e[i].v;
        if(v==fa[u]) continue;
        dfs(v);
        rt[u]=merge(rt[u],rt[v]);
        mi[u]=min(mi[u],mi[v]);
    }
}
int main()
{
    cnt=last=1;
    n=read();scanf("%s",s);
    for(int i=0;i<n;i++) add(s[i]-'0');
    for(int i=2;i<=cnt;i++)
    {
        if(!mi[i]) mi[i]=n+1;
        e[++tot]=edge(i,f[fa[i]]),f[fa[i]]=tot;
        e[++tot]=edge(fa[i],f[i]),f[i]=tot;
    }
    dfs(1);
    m=read();
    while(m--)
    {
        scanf("%s",s),k=strlen(s);
        int p=1,F=0;long long ans=0;
        for(int i=0;i<k;i++)
        {
            int c=s[i]-'0';
            if(!ch[p][c]) {F=0;break;}
            p=ch[p][c];F=mi[p];
        }
        if(!F) ans=n;
        else ans=F-k;
        p=1;
        for(int i=0;i<k;i++)
        {
            int c=s[i]-'0';
            if(!ch[p][c]) break;
            p=ch[p][c];
            ans+=ask(rt[p],1,n,1,(!F)?n:F-k+i+1);
            //注意是从0开始的,所以还要加个1
        }
        printf("%lld\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值