zoukankan      html  css  js  c++  java
  • 多串匹配

    meteor多串匹配

    Description

    Input

    第一行为一个整数n,表示文本的长度 
    第二行为一个长度为n的文本 
    第三行为一个整数m,表示模式串个数 
    下接m行,每行一个模式串 

    Output

    共m行,若第i个模式串在文本中出现过则第i行输出YES,否则输出NO 
    数据范围 
    对于30%的数据,n<=10^3,m<=10^3; 
    对于80%的数据,n<=10^5,m<=10^4; 
    对于100%的数据,n<=10^5,m<=10^5。 
    输入文件大小不超过1M。 

    Sample Input

    25
    saintzeuscynthiathenahere
    3
    cynthia
    hera
    athena
    

    Sample Output

    YES
    NO
    YES
    

    Source

    后缀数组

    给定一个固定待匹配串S,长度为n,然后每次输入一个模式串P,长度为
    m,要求返回P 在S 中的一个匹配或者返回匹配失败。所谓匹配指某个位置i
    满足1≤i≤n-m+1 使得S[i..(i+m-1)]=P,也即Suffix(i)=mP。
    我们知道,如果只有一个模式串,最好的算法就是KMP 算法,时间复杂
    度为O(n+m),但是如果有多个模式串,我们就要考虑做适当的预处理使得对每
    个模式串进行匹配所花的时间小一些。
    最简单的预处理莫过于建立S 的后缀数组(先在S 的后面添加'$'),然后每
    次寻找匹配转化为用二分查找法在SA 中找到和P 的公共前缀最长的一个后缀,
    判断这个最长的公共前缀是否等于m。
    这样,每次比较P 和一个后缀的复杂度为O(m),因为最坏情况下可能比较
    了m 个字符。二分查找需要调用比较的次数为O(logn),因此总复杂度为
    O(mlogn),于是每次匹配的复杂度从O(n+m)变为O(mlogn),可以说改进了不
    少。
    可是这样仍然不能令我们满足。前面提到LCP 可以增加后缀数组的威力,
    我们来试试用在这个问题上。
    我们分析原始的二分查找算法,大体有以下几步:

    Step 1 令 left=1,right=n,max_match=0。
    Step 2 令 mid=(left+right)/2(这里“/”表示取整除法)。
    Step 3 顺次比较Suffix(SA[mid])和P 的对应字符,找到两者的最长公共
    前缀r,并判断出它们的大小关系。若r>max_match则令max_match=r,ans=mid。
    Step 4 若Suffix(SA[mid])<P 则令left=mid+1,若Suffix(SA[mid])>P 则令
    right=mid-1,若Suffix(SA[mid])=P 则转至Step 6。
    Step 5 若 left<right 则转至Step 2,否则至Step 6。
    Step 6 若 max_match=m则输出ans,否则输出“无匹配”。
    注意力很快集中在Step 3,如果能够避免每次都从头开始比较Suffix(SA[mid])
    和P 的对应字符,也许复杂度就可以进一步降低。
    类似于前面求height 数组,我们考虑利用以前求得的最长公共前缀作为比
    较的“基础”,避免冗余的字符比较。

    在比较Suffix(SA[mid])和P 之前,我们先用常数时间计算LCP(mid,ans),
    然后比较LCP(mid,ans)和max_match:
    情况一:LCP(mid,ans)<max_match,则说明Suffix(SA[mid])和P 的最长公
    共前缀就是LCP(mid,ans),即直接可以确定Step 3 中的r=LCP(mid,ans),所以
    可以直接比较两者的第r+1 个字符(结果一定不会是相等)就可以确定
    Suffix(SA[mid])和P 的大小。这种情况下,字符比较次数为1 次。
    情况二:LCP(mid,ans)≥max_match,则说明Suffix(SA[mid])和Suffix(SA[ans])
    的前max_match个字符一定是相同的,于是Suffix(SA[mid])和P 的前max_match
    个字符也是相同的,于是比较两者的对应字符可以从第max_match+1 个开始,
    最后求出的r 一定大于等于原先的max_match,字符比较的次数为rmax_
    match+1,不难看出Step 3 执行过后max_match将等于r。
    设每次Step 3 执行之后max_match 值增加的量为Δmax。在情况一中,
    Δmax=0,字符比较次数为1=Δmax+1;在情况二中,Δmax=r-max_match,字符
    比较次数为r-max_match+1,也是Δmax+1。综上所述,每次Step 3 进行字符比
    较的次数为Δmax+1。

    总共的字符比较次数为所有的Δmax累加起来再加上Step 3执行的次数。所
    有Δmax累加的结果显然就是最后的max_match值,不会超过len(P)=m,而Step
    3 执行的次数为O(logn),因此总共的字符比较次数为O(m+logn)。而整个算法
    的复杂度显然和字符比较次数同阶,为O(m+logn)。
    至此,问题得到圆满解决,通过O(nlogn)的时间进行预处理(构造后缀数
    组、名词数组,计算height 数组,RMQ 预处理),之后就可以在O(m+logn)的
    时间内对一个长度为m 的模式串P 进行模式匹配,这仅仅是在读入P 的复杂度
    上附加了logn的复杂度,是非常优秀的。

    code:

    #include<cstdio>
    #include<iostream>
    #include<cmath>
    #include<cstring>
    #define maxn 100010
    using namespace std;
    char ch,s[maxn],ss[maxn];
    int n,m,l,r,mid,len,tot,max_match,t_len,ans;
    int SA[maxn],rank[maxn<<1],tmp[maxn],sum[maxn],height[maxn],tree[maxn<<2];
    bool bo,stop;
    void read(int &x){
        for (ch=getchar();!isdigit(ch);ch=getchar());
        for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    }
    void get_SA(){
        int *x=t1,*y=t2;
        for (int i=1;i<=n;i++) sum[x[i]=s[i]]++;
        for (int i=1;i<=255;i++) sum[i]+=sum[i-1];
        for (int i=1;i<=n;i++) SA[sum[x[i]]--]=i;
        tot=0;
        for (int len=1;tot<n;len<<=1,m=tot){
            tot=0;
            for (int i=n-len+1;i<=n;i++) y[++tot]=i;
            for (int i=1;i<=n;i++) if (SA[i]>len) y[++tot]=SA[i]-len;
            memset(sum,0,sizeof(sum));
            for (int i=1;i<=n;i++) sum[x[y[i]]]++;
            for (int i=1;i<=m;i++) sum[i]+=sum[i-1];
            for (int i=n;i>=1;i--) SA[sum[x[y[i]]]--]=y[i];
            swap(x,y);
            x[SA[1]]=tot=1;
            for (int i=2;i<=n;i++){
                if (y[SA[i]]!=y[SA[i-1]]||y[SA[i]+len]!=y[SA[i-1]+len]) tot++;
                x[SA[i]]=tot;    
            }
        }
        for (int i=1;i<=n;i++) rank[i]=x[i];
    }
    void get_height(){
        for (int i=1,j=0;i<=n;i++){
            if (rank[i]==1) continue;
            while (s[i+j]==s[SA[rank[i]-1]+j]) j++;
            height[rank[i]]=j;
            if (j>0) j--;    
        }    
    }
    inline void build(int k,int l,int r){
        if (l==r){
            tree[k]=height[l];
            return;
        }    
        int m=(l+r)>>1;
        build(k<<1,l,m),build((k<<1)+1,m+1,r);
        tree[k]=min(tree[k<<1],tree[(k<<1)+1]);
    }
    inline int answer(int k,int l,int r,int x,int y){
        if (l==x&&r==y) return tree[k];
        int m=(l+r)>>1;
        if (y<=m) return answer(k<<1,l,m,x,y);
        else if (x<=m) return min(answer(k<<1,l,m,x,m),answer((k<<1)+1,m+1,r,m+1,y));    
        else return answer((k<<1)+1,m+1,r,x,y);
    }
    inline int LCP(int i,int j){
        if (i==j) return n-SA[i]+1;
        if (i>j) swap(i,j);
        return answer(1,1,n,i+1,j);    
    }
    int main(){
        read(n);
        scanf("%s",s+1);
        get_SA(),get_height(),build(1,1,n);
        read(m);
        for (int i=1;i<=m;i++){
            scanf("%s",ss+1);
            len=strlen(ss+1);
            l=1,r=n,max_match=ans=0;
            while (l<=r){
                mid=(l+r)>>1,bo=0,stop=0;
                if (!ans){
                    int i;
                    for (i=SA[mid];i<=n&&ss[max_match+1]==s[i];i++,max_match++,ans=mid);
                    if (s[i]<ss[max_match+1]) bo=1;
                    
                }
                else{
                    t_len=LCP(ans,mid);
                    if (t_len<max_match){
                        if (s[t_len+SA[mid]]<ss[t_len+1]) bo=1;
                    }
                    else{
                        int i;
                        for (i=SA[mid]+max_match;i<=n&&ss[max_match+1]==s[i];i++,max_match++,ans=mid);
                        if (s[i]<ss[max_match+1]) bo=1;
                    }
                }
                if (max_match>=len) break;
                if (bo) l=mid+1;
                else r=mid-1;
            }
            if (max_match>=len) puts("YES");
            else puts("NO");
        }
        return 0;    
    }
  • 相关阅读:
    使用vagrant一键部署本地php开发环境(一)
    产品化机器学习的一些思考
    突破、进化,腾讯云数据库2018全年盘点
    WebGL 纹理颜色原理
    如何定制Linux外围文件系统?
    一文了解腾讯云数据库SaaS服务
    如何正确的选择云数据库?
    Node.js 进程平滑离场剖析
    Git合并不同url的项目
    mariadb 内存占用优化
  • 原文地址:https://www.cnblogs.com/chenyushuo/p/4124847.html
Copyright © 2011-2022 走看看