2019年华南理工大学程序设计竞赛(春季赛)K(后缀自动机)

传送门

题意:

给你一个长度位lenlen的串,一共有tt组询问,每次询问给你一个数ii,你要将原来的串在位置ii处分开,构造出两个不同的字符串str1str_1str2str_2。现在要问你str1str_1str2str_2的公共子串的个数。

分析:

讲真这又是一个简化版的原题呀 。(原题poj3415poj3415

我们发现,在这个题中,询问非常多,字符串的长度并不是非常的大。因此我们可以考虑先把答案预处理出来。

因为涉及两个串的子串个数问题,因此我们不妨考虑使用后缀自动机预处理。

我们对其中一个串str1str_1建立后缀自动机,我们用拓扑排序自底向上的对整个自动机的endposendpos集合的大小numnum进行更新(因为对于某个结点pp,他沿着后缀连接走到的结点fa(p)fa(p)必然是结点pp的后缀,故结点pp的答案必定要更新到结点fa(p)fa(p))。

更新完endposendpos集合的大小numnum后,我们需要维护一个前缀和sum(p)sum(p),代表到达当前结点pp时,终止点在endposendpos的所有子串中,在串str1str_1中出现的次数。

完成上述工作之后,我们只需要把str2str_2串丢到后缀自动机上去匹配,每次去统计答案并记录下来供tt次询问查询即可。

整体时间复杂度:O(len2)\mathcal{O}(len^2)

代码:

#include <bits/stdc++.h>
#define maxn 4005
using namespace std;
char str[maxn];
typedef unsigned long long ll;
struct SAM{
    int next[maxn*2][26],fa[maxn*2],len[maxn*2],last,cnt=0;
    ll cntA[maxn*2],A[maxn*2];
    ll num[maxn*2],sum[maxn*2];
    SAM(){clear();}
    void clear(){
        last=cnt=1;
        memset(sum,0,sizeof(sum));
        memset(num,0,sizeof(num));
        memset(next[1],0,sizeof(next[1]));
        memset(len,0,sizeof(len));
        fa[1]=len[1]=0;
    }
    void Insert(int c){
        int p=last;
        int np=++cnt;
        num[np]=1;
        memset(next[cnt],0,sizeof(next[cnt]));
        len[np]=len[p]+1;last=np;
        while(p&&!next[p][c]) next[p][c]=np,p=fa[p];
        if(!p) fa[np]=1;
        else{
            int q=next[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else{
                int nq=++cnt;
                len[nq]=len[p]+1;
                memcpy(next[nq],next[q],sizeof next[nq]);
                fa[nq]=fa[q]; fa[np]=fa[q]=nq;
                while(next[p][c]==q) next[p][c]=nq,p=fa[p];
            }
        }
    }
    void build(char *s){
        memset(cntA,0,sizeof(cntA));
        memset(A,0,sizeof(A));
        for(int i=1;i<=cnt;i++) cntA[len[i]]++;
        for(int i=1;i<=cnt;i++) cntA[i]+=cntA[i-1];
        for(int i=cnt;i;i--) A[cntA[len[i]]--] =i;
        int tmp=1;
        int sz=strlen(s);
        for(int i=0;i<sz;i++){
            num[tmp=next[tmp][s[i]-'a']]=1;
        }
        for(int i=cnt;i;i--){
            int x=A[i];
            num[fa[x]]+=num[x];
        }
        for(int i=1;i<=cnt;i++){
            int x=A[i];
            sum[x]=sum[fa[x]]+num[x]*(len[x]-len[fa[x]]);
        }
    }
    int query(char *s){
        int sz=strlen(s),now=1,Len=0;
        int res=0;
        for(int i=0;i<sz;i++){
            int c=s[i]-'a';
            if(next[now][c]){
                Len++;
                now=next[now][c];
            }
            else{
                while(now&&!next[now][c]) now=fa[now];
                if(now){
                    Len=len[now]+1;
                    now=next[now][c];
                }
                else{
                    Len=0,now=1;
                }
            }
            res+=sum[fa[now]]+num[now]*(Len-len[fa[now]]);
        }
        return res;
    }
}sam;
ll ans[maxn];
char tmp1[maxn];
char tmp2[maxn];
int main()
{
    //freopen("in.txt","r",stdin);
    scanf("%s",str);
    int t;
    scanf("%d",&t);
    int len=strlen(str);
    for(int i=0;i<len-1;i++){
        memset(tmp1,0,sizeof(tmp1));
        memset(tmp2,0,sizeof(tmp2));
        for(int j=0;j<=i;j++) tmp1[j]=str[j];
        for(int j=i+1;j<len;j++) tmp2[j-i-1]=str[j];
        sam.clear();
        int sz=strlen(tmp1);
        for(int i=0;i<sz;i++){
            sam.Insert(tmp1[i]-'a');
        }
        sam.build(tmp1);
        ans[i+1]=sam.query(tmp2);
    }
    while(t--){
        int n;
        scanf("%d",&n);
        printf("%lld\n",ans[n]);
    }
    return 0;
}

posted @ 2019-04-15 18:42  ChenJr  阅读(228)  评论(0编辑  收藏  举报