bzoj5337 [TJOI2018]str 后缀自动机

该博客主要介绍了如何运用后缀自动机解决bzoj5337题目的过程。通过建立原串的后缀自动机,利用动态规划(DP)方法计算在不同节点上的方案数。当输入串T与自动机匹配成功,即到达新的节点now时,更新方案数f[i][now] += f[i-1][p]。最终,将所有节点的方案数乘以特定集合的大小得出答案。

传送门
原串建立后缀自动机。用输入的串在上面跑DP。
f [ i ] [ j ] f[i][j] f[i][j]表示已经确定了前 i i i个氨基酸,并且当前在后缀自动机的 j j j节点上 的方案数。
那么我们假设之前 i i i个氨基酸已经确定,而现在输入了一个串 T T T,我们考虑暴力从自动机上的每个节点开始按照 T T T往下走。
假设当前枚举到节点 p p p。如果走不下去了就退出。然而如果走完了 T T T,到达节点 n o w now now,就表示接上 T T T之后可以和原串匹配。
那么如果可以接上 T T T,转移方程就是 f [ i ] [ n o w ] + = f [ i − 1 ] [ p ] f[i][now]+=f[i-1][p] f[i][now]+=f[i1][p]

得到每个自动机上每个节点(由k个串组合得到)的方案数后,乘上endpos集合的大小即可(代码中对应right)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e4+10;
const int mod=1e9+7;
char N[maxn];int f[105][maxn<<1],sum[maxn<<1],t[maxn<<1];
int last,sz,k,n,T,ans=0;
struct node{int len,link,right,nxt[26];}st[maxn<<1];
inline int add(int x,int y){return (x+y>=mod)?(x+y-mod):(x+y);}
inline int mul(int x,int y){return (ll)x*y%mod;}
inline void init(){last=sz=0,st[0].right=st[0].link=-1,st[0].len=0;}
inline void build(int c){
    int cur=++sz,p=last;
    st[cur].len=st[last].len+1,st[cur].right=1;
    for(;p!=-1&&!st[p].nxt[c];p=st[p].link)
        st[p].nxt[c]=cur;
    if(p==-1) st[cur].link=0;
    else{
        int q=st[p].nxt[c];
        if(st[p].len+1==st[q].len) st[cur].link=q;
        else{
            int clone=++sz;st[clone]=st[q];
            st[clone].right=0,st[clone].len=st[p].len+1;
            for(;p!=-1&&st[p].nxt[c]==q;p=st[p].link)
                st[p].nxt[c]=clone;
            st[cur].link=st[q].link=clone;
        }
    }last=cur;
}
inline void calc_right(){
    memset(sum,0,sizeof(sum));
    for(int i=1;i<=sz;++i) sum[st[i].len]++;
    for(int i=1;i<=n;++i) sum[i]+=sum[i-1];
    for(int i=sz;i>=1;i--) t[sum[st[i].len]--]=i;
    for(int i=sz;i>=1;i--)
        st[st[t[i]].link].right+=st[t[i]].right;
}
int main(){
    scanf("%d%s",&k,N),n=strlen(N),init();
    for(int i=0;i<n;++i) build(N[i]-'A');
    f[0][0]=1,calc_right();
    for(int i=1;i<=k;++i){
        scanf("%d",&T);
        while(T--){
            scanf("%s",N),n=strlen(N);
            for(int p=0;p<=sz;++p){
                int now=p;
                for(int q=0;q<n;++q){
                    if(st[now].nxt[N[q]-'A'])
                        now=st[now].nxt[N[q]-'A'];
                    else{now=-1;break;}
                }if(now!=-1) f[i][now]=add(f[i][now],f[i-1][p]);
            }
        }
    }
    for(int i=1;i<=sz;++i) ans=add(ans,mul(st[i].right,f[k][i]));
    cout<<ans<<'\n';
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值