[2018雅礼集训1-4]字符串 AC自动机+状压DP

题面
其实我的写法有些复杂。。。
考虑一个反对称串 T m位确定,后 m 位也唯一确定了,就是翻转之后再01取反。所以给定的一个串要在T中出现过,要么是前半部分的子串,要么是后半部分的子串(等价于翻转之后再01取反后是前半部分的子串),要么经过中间。
先考虑前两种情况,想到建出AC自动机再状压DP,我们从第 m 位往前DP,AC自动机上要建出每个给定串s的翻转串和01取反串,并打上该串的标记,并且这个标记要沿fail指针下传。对于第三种情况,我们枚举 T 串中点切在s的哪个位置,首先 s 必须以其为中心反对称,接着我们假设切点左边比右边长(如果右边长就先对称过来),于是假如DP时以这个偏长的串为前缀(也就是在AC自动机上未经过fail指针就走到了),那么s也就被包含了。
具体地,设计状态 f[i][j][s][0/1] 表示DP了 i 位,在AC自动机上j点,已经被包含的串的状态是 s ,当前未经过/经过了fail指针的方案数。DP的时候s或上当前点的标记,并且注意最后一维0/1即可。
其实直接从前往后DP,DP完之后在把分在中点两边的串的标记或上去会好写些。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int mod=998244353;
const int maxp=3010;
int n,m,cnt,cs[110],len[8],a[maxp][2],fa[maxp],pl[maxp],uf[maxp],dl[maxp],f[2][maxp][70][2];
bool bk[maxp][2];
char s[8][110];
void ins(int *t,int len,int id,int* lab)
{
    int p=0;
    for(int i=1;i<=len;p=a[p][t[i]],i++)
        if(!a[p][t[i]]) bk[p][t[i]]=1,a[p][t[i]]=++cnt;
    lab[p]|=(1<<(id-1)); 
}
void check(int *t,int len,int id,int d)
{
    for(int i=1;i<=len;i++)
    {
        if(2*d-i+1>0&&2*d-i+1<=len&&t[i]==t[2*d-i+1]) {return ;}
    }
    int w[110];
    if(d>len/2)
    {
        for(int i=1;i<=d;i++)
            w[i]=t[d-i+1];
        ins(w,d,id,uf); 
    }
    else
    {
        for(int i=d+1;i<=len;i++)
            w[i-d]=(t[i]^1);
        ins(w,len-d,id,uf);
    }
}
void getac()
{
    for(int hd=1,tl=1,v=0;hd<=tl;v=dl[++hd])
        for(int ic=0;ic<=1;ic++)
            if(a[v][ic])
            {
                int p=fa[v];
                while(p&&!a[p][ic]) p=fa[p];
                if(a[p][ic]!=a[v][ic]) fa[a[v][ic]]=a[p][ic],pl[a[v][ic]]|=pl[a[p][ic]];
                dl[++tl]=a[v][ic];
            }
            else a[v][ic]=a[fa[v]][ic];             
}
void dp()
{
    memset(f,0,sizeof(f));
    f[0][0][pl[0]|uf[0]][0]=1;
    for(int i=1,v=1;i<=m;v^=1,i++)
    {
        memset(f[v],0,sizeof(f[v]));
        for(int j=0;j<=cnt;j++)
            for(int s=0;s<(1<<n);s++)
                if(f[v^1][j][s][0]||f[v^1][j][s][1])
                for(int ic=0;ic<=1;ic++)
                {
                    int to=a[j][ic];
                    (f[v][to][ pl[to] | s ][1]+=f[v^1][j][s][1])%=mod;
                    if(bk[j][ic]) (f[v][to][ pl[to] | uf[to] | s ][0]+=f[v^1][j][s][0])%=mod;
                    else (f[v][to][ pl[to] | s ][1]+=f[v^1][j][s][0])%=mod;
                }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s[i]+1);
        len[i]=strlen(s[i]+1);
        for(int j=1;j<=len[i];j++)
            cs[j]=s[i][len[i]-j+1]-'0';
        ins(cs,len[i],i,pl);

        for(int j=1;j<=len[i];j++)
            cs[j]=((s[i][j]-'0')^1);
        ins(cs,len[i],i,pl);

        for(int j=1;j<=len[i];j++)
            cs[j]=s[i][j]-'0';
        for(int j=1;j<len[i];j++)
            check(cs,len[i],i,j);   
    }
    getac();
    dp();
    int ans=0;
    for(int i=0;i<=cnt;i++)
        (ans+=f[m&1][i][(1<<n)-1][0])%=mod,(ans+=f[m&1][i][(1<<n)-1][1])%=mod;
    printf("%d",ans);   
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值