BZOJ3277: 串 (广义SAM)

传送门

题解:
后缀数组的做法就不说了,又慢又长。

考虑后缀自动机,把这几个串同时拿出来搞。

多个串怎么建呢? 一种方法是直接每个字符中串一个字符,不过这样会导致字符集变大,还有一种方法是直接每次暴力重置last为root,插入的时候判一下是否有转移。

我们还要统计一个点对于每个串的出现次数,这个也有两种方法,一种是离线之后用树状数组搞,具体细节就不说了。 还有一种复杂度不知道正不正确的方法,就是每次插入完后直接暴力跳fail链统计,注意打个标号,如果已经被这个串统计过就不继续跳fail了。。

总之复杂度很像 O(n) O ( n ) 的,而且也不好卡。这种写法比后缀数组快了10多倍。

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
int n,k,last=1,l[N],ld[N],fail[N],son[N][26],sze[N],tot;
long long f[N];
int bg[N],ed[N],a[N],totlen;
char ch[N];
inline void extend(int c,int id) {
    if(son[last][c]) {
        int p=son[last][c];
        if(l[p]==l[last]+1) last=p;
        else {
            int np=++tot; l[np]=l[last]+1; 
            sze[np]=sze[p]; ld[np]=ld[p];
            memcpy(son[np],son[p],sizeof(son[p]));
            fail[np]=fail[p]; fail[p]=np;
            while(last&&son[last][c]==p) son[last][c]=np,last=fail[last];
            last=np;
        }   
    } else {
        int p=++tot; l[p]=l[last]+1;
        while(last&&!son[last][c]) son[last][c]=p,last=fail[last];
        if(!last) fail[p]=1;
        else {
            int q=son[last][c];
            if(l[q]==l[last]+1) fail[p]=q;
            else {
                int np=++tot; l[np]=l[last]+1;
                sze[np]=sze[q]; ld[np]=ld[q];
                memcpy(son[np],son[q],sizeof(son[q]));
                fail[np]=fail[q]; fail[q]=np; fail[p]=np;
                while(last&&son[last][c]==q) son[last][c]=np,last=fail[last];
            }
        }
        last=p;
    }
    for(int now=last;now&&ld[now]!=id;now=fail[now]) {
        ld[now]=id; ++sze[now];
    }
}
inline void insert(int id) {
    last=1; int len=strlen(ch+1);
    for(int i=1;i<=len;i++)
        extend(ch[i]-'a',id);
    bg[id]=totlen+1; ed[id]=totlen+len;
    for(int i=1;i<=len;i++) a[i+totlen]=ch[i]-'a';
    totlen=ed[id];
}
int que[N],tl,deg[N];
int main() {
    scanf("%d%d",&n,&k); tot=1;
    for(int i=1;i<=n;i++) {
        scanf("%s",ch+1);
        insert(i);
    }
    for(int i=1;i<=tot;i++){ if(sze[i]>=k) f[i]=(l[i]-l[fail[i]]); }
    for(int i=1;i<=tot;i++) if(fail[i]) ++deg[fail[i]];
    for(int i=1;i<=tot;i++) if(!deg[i]) que[++tl]=i;
    for(int i=1;i<=tl;i++) 
        if(fail[que[i]]) 
            if(!(--deg[fail[que[i]]]))
                que[++tl]=fail[que[i]];
    for(int i=tl;i>=1;i--) if(fail[que[i]]) f[que[i]]+=f[fail[que[i]]];
    for(int i=1;i<=n;i++) {
        long long sum=0; int nowpos=1;
        for(int j=bg[i];j<=ed[i];j++) {
            nowpos=son[nowpos][a[j]];
            sum+=f[nowpos];
        } 
        printf("%lld ",sum);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值