后缀数组/AC自动机——BZOJ3172 [Tjoi2013]单词

题面:BZOJ3172
2017.6.9 SA做法:
首先把论文给造出来是吧。。。(单词中间加空格好了)
然后在这篇文章(其实是一串字符串)求Height(后缀数组实现)
把Height数组求出来之后我们对于每个单词暴力向左向右找相邻的lcp,如果Height[i]大于等于单词长的话,那么这个串在那个后缀中出现了(而且是在前缀位置),如果小于了那就说明后面的都没有了,直接跳出
因为相邻的Height肯定比不相邻的Height大,所以具有连续的性质,所以可以证明这样是对的
把向左向右扫到的答案直接输出就行了

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <ctime>
#include <map>
#include <queue>
#include <cstdlib>
#include <string>
#include <climits>
#include <set>
#include <vector>
using namespace std;
int n,m,q,rank[2000001],sa[2000001],a[2000001],rank1[2000001],h[2000001];
int ton[2000001],L[20001],R[20001];
char s[2000001],c[20001];
inline void Sort(){
    for(int i=0;i<=m;i++)ton[i]=0;
    for(int i=1;i<=n;i++)ton[rank[rank1[i]]]++;
    for(int i=1;i<=m;i++)ton[i]+=ton[i-1];
    for(int i=n;i;i--)sa[ton[rank[rank1[i]]]--]=rank1[i];
}
int main()
{
    scanf("%d",&q);n=0;
    for(int i=1;i<=q;i++){
        scanf("%s",c+1);int l=strlen(c+1);
        L[i]=n+1;R[i]=n+l;
        for(int j=1;j<=l;j++)s[++n]=c[j];s[++n]=' ';
    }n--;
    for(int i=1;i<=n;i++)a[i]=s[i];
    for(int i=1;i<=n;i++)rank[i]=a[i],rank1[i]=i;
    m=127;Sort();
    for(int i=1,j,p=1;p<n;i*=2,m=p){
        for(p=0,j=n-i+1;j<=n;j++)rank1[++p]=j;
        for(j=1;j<=n;j++)if(sa[j]>i)rank1[++p]=sa[j]-i;
        Sort();swap(rank,rank1);rank[sa[1]]=p=1;
        for(j=2;j<=n;j++)rank[sa[j]]=(rank1[sa[j]]==rank1[sa[j-1]]&&rank1[sa[j]+i]==rank1[sa[j-1]+i])?p:++p;
    }
    for(int i=1,j=0,k=0;i<=n;h[rank[i++]]=k)
        for(k=k?k-1:k,j=sa[rank[i]-1];a[i+k]==a[j+k];++k);
    for(int i=1;i<=q;i++){
        int l=R[i]-L[i]+1;int ans=1;
        for(int j=rank[L[i]];h[j]>=l;j--,ans++);
        for(int j=rank[L[i]];h[j+1]>=l;j++,ans++);
        printf("%d\n",ans);
    }
    return 0;
}

2017.12.10 AC自动机做法:
哇总算用AC自动机把这题过了QAQ
AC自动机做法这题死磕三天QAQ
说起来是很简单,很裸地跑一遍就好了
但是据说这题是在卡普通的AC自动机,Luogu一直90分,BZOJ一直TLE
第二天的时候发现我的AC自动机写法太慢了,于是换了一种写法
第三天又发现光是直接跳fail找串太慢了。。。于是把有串的节点记一下,然后跳的时候直接跳到这些点就好了QAQ
然后就AC了。。。

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <ctime>
#include <map>
#include <queue>
#include <cstdlib>
#include <string>
#include <climits>
#include <set>
#include <vector>
using namespace std;
inline int read(){
    int k=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){k=k*10+ch-'0';ch=getchar();}
    return k*f;
}
inline void write(int x){
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10);putchar(x%10+'0');
}
inline void writeln(int x){
    write(x);puts("");
}
int n,nex[2000010][26],cnt=0,L=0,fail[2000010],failp[2000010],ans[2000010],rk[2000010],p[2000010],Cnt=0;
char s[2000010],z[2000010];
inline void insert(int x){
    scanf("%s",s+1);int l=strlen(s+1);
    int now=0;
    for(int i=1;i<=l;i++)z[i+L]=s[i];
    L+=l;z[++L]=' ';
    for(int i=1;i<=l;i++){
        int t=s[i]-'a';
        if(!nex[now][t])nex[now][t]=++cnt;
        now=nex[now][t];
    }
    if(!p[now])p[now]=++Cnt;
    rk[x]=p[now];
}
queue<int>q;
inline void bfs(){
    for(int i=0;i<26;i++)if(nex[0][i])q.push(nex[0][i]);
    while(!q.empty()){
        int now=q.front();q.pop();
        for(int i=0;i<26;i++)if(nex[now][i]){
            fail[nex[now][i]]=nex[fail[now]][i];//记fail指针
            failp[nex[now][i]]=p[fail[nex[now][i]]]?fail[nex[now][i]]:failp[fail[nex[now][i]]];//failp就是在记有fail的是原串结尾的节点
            q.push(nex[now][i]);
        }else nex[now][i]=nex[fail[now]][i];
    }
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)insert(i);
    bfs();
    int now=0;
    for(int i=1;i<=L;i++){
        if(z[i]==' '){now=0;continue;}
        int t=z[i]-'a';
        if(nex[now][t])ans[p[nex[now][t]]]++;
        int la=failp[nex[now][t]];//跳failp时间效率会快不少
        while(la){
            ans[p[la]]++;
            la=failp[la];
        }
        now=nex[now][t];
    }
    for(int i=1;i<=n;i++)writeln(ans[rk[i]]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值