【HDU6086】Rikka with String-AC自动机+状压DP
测试地址:Rikka with String
题目大意:给定个串,问有多少长为的反对称串包含这个串作为子串。一个串反对称当且仅当对于每个字符都有。
做法:本题需要用到AC自动机+状压DP。
首先我们转化一下题目中的条件。一个串在一个反对称串中出现的情况有三种:
1.全部出现在左半部分;
2.全部出现在右半部分;
3.出现在中间。
第一和第二种情况很好判断,直接把正串和反串丢进AC自动机里标记一下就行了,主要是第三种情况。
因为整个串是反对称的,所以它有一个对称轴,我们对于每个给出的串,枚举对称轴可能的位置,这样对于每一个可能的位置,都可以得到一个新的条件:当一个反对称串在前半部分出现某一个后缀时,就代表该串在反对称串的中间部分出现了。注意到这样的后缀要么是原串的正串的前缀,要么反串的前缀,所以我们也直接在AC自动机上打标记即可。注意上述出现某个串的标记不能只打在一个点上,而是要下放到它在fail树上的子树中。
接下来就可以DP了。令为在AC自动机上匹配了步,当前走到点,匹配过的串的状态为(是一个位的二进制数),那么状态转移就很显然了。最后统计答案的时候,对于一个状态,如果算上AC自动机中状态包含的所有后缀标记,加上状态中已经匹配的串,匹配全了所有的串,那么将累加进答案。
以上算法的时间复杂度是,可以通过此题。
题外话:这题是学校训练中出现的,本蒟蒻比较好奇有没有原题,结果还真的找到了,于是就写在这里。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod=998244353;
int T,n,m,rt,tot,len,pos[2010],lastpos[2010];
int h,t,q[2010];
int ch[2010][2],fail[2010],nxt[2010][2];
int tote=0,first[2010];
ll f[2][2010][70];
char s[110],ps[110];
int forb[2010],forbs[2010];
struct edge
{
int v,next;
}e[10010];
void insert(char *s,int &v,int step)
{
if (!v) v=++tot;
if (step>=0) pos[step]=v;
if (step>=len-1) return;
insert(s,ch[v][s[step+1]-'0'],step+1);
}
void insertedge(int a,int b)
{
e[++tote].v=b;
e[tote].next=first[a];
first[a]=tote;
}
void init()
{
memset(first,0,sizeof(first));rt=tot=tote=0;
memset(ch,0,sizeof(ch));
memset(forb,0,sizeof(forb));
memset(forbs,0,sizeof(forbs));
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%s",s);
len=strlen(s);
insert(s,rt,-1);
forb[pos[len-1]]|=(1<<i);
forbs[pos[len-1]]|=(1<<i);
for(int j=0;j<len;j++)
{
lastpos[j]=pos[j];
ps[j]=(!(s[len-j-1]-'0'))+'0';
}
insert(ps,rt,-1);
forb[pos[len-1]]|=(1<<i);
forbs[pos[len-1]]|=(1<<i);
for(int j=0;j<len-1;j++)
{
bool flag=1;
for(int k=0;j-k>=0&&j+k+1<len;k++)
if (s[j-k]==s[j+k+1]) {flag=0;break;}
if (flag)
{
if (len-j-2>j) forbs[pos[len-j-2]]|=(1<<i);
else forbs[lastpos[j]]|=(1<<i);
}
}
}
}
void build()
{
h=t=q[1]=1;
fail[1]=0;
while(h<=t)
{
int v=q[h++];
for(int i=0;i<=1;i++)
if (ch[v][i])
{
int p=fail[v];
while(p&&!ch[p][i]) p=fail[p];
if (p) fail[ch[v][i]]=ch[p][i];
else fail[ch[v][i]]=1;
insertedge(fail[ch[v][i]],ch[v][i]);
q[++t]=ch[v][i];
}
}
}
void pushdown(int v,int f,int fs)
{
f=forb[v]=f|forb[v];
fs=forbs[v]=fs|forbs[v];
for(int i=first[v];i;i=e[i].next)
pushdown(e[i].v,f,fs);
}
void work()
{
for(int i=1;i<=tot;i++)
for(int j=0;j<=1;j++)
{
int p=i;
while(p&&!ch[p][j]) p=fail[p];
if (p) nxt[i][j]=ch[p][j];
else nxt[i][j]=1;
}
}
void dp()
{
int now=0,past=1;
memset(f,0,sizeof(f));
f[past][1][0]=1;
for(int i=1;i<=m;i++)
{
memset(f[now],0,sizeof(f[now]));
for(int j=1;j<=tot;j++)
for(int s=0;s<(1<<n);s++)
{
f[now][nxt[j][0]][s|forb[nxt[j][0]]]=
(f[now][nxt[j][0]][s|forb[nxt[j][0]]]+f[past][j][s])%mod;
f[now][nxt[j][1]][s|forb[nxt[j][1]]]=
(f[now][nxt[j][1]][s|forb[nxt[j][1]]]+f[past][j][s])%mod;
}
swap(now,past);
}
ll ans=0;
for(int i=1;i<=tot;i++)
for(int s=0;s<(1<<n);s++)
if ((s|forbs[i])==(1<<n)-1) ans=(ans+f[past][i][s])%mod;
printf("%lld\n",ans);
}
int main()
{
scanf("%d",&T);
while(T--)
{
init();
build();
pushdown(1,0,0);
work();
dp();
}
return 0;
}