我好菜啊。。。都是抄了std后再去理解的。。。
补集思想,先求出所有不可读的,在减去即可。
f[i,j]表示在AC自动机上走i步到达j点构成单词的方案数。首先要把所有是单词结尾的点标记出来,是不能走的,而且沿着fail树能走到单词末尾的点也是不能走的,这个在建AC自动机的时候顺便处理一下。然后可以对AC自动机做一个小处理,如果trie[i,c]=0(不存在)那么就把trie[fail[i],c]赋给它,最后dp的时候就不要管fail树了。
转移便是f[i, trie[j,c]]=f[i, trie[j, c]]+f[i-1, j]。
我一直不是很理解哪些没有出现过的字母是怎么搞出来的,后来发现全在0上。。。
代码:
const
maxn=6000;
maxm=105;
md=10007;
var
n,m,i,tot,ans:longint;
trie:array[0..maxn,'A'..'Z']of longint;
fail:array[0..maxn]of longint;
s:ansistring;
f:array[0..maxm,0..maxn] of longint;
place:array[-1..26*maxn]of boolean;
procedure ins(s:ansistring);
var
i,len,p:longint;
begin
p:=0;
len:=length(s);
for i:=1 to len do
begin
if trie[p,s[i]]=0 then begin inc(tot); trie[p,s[i]]:=tot; end;
p:=trie[p,s[i]];
end;
place[p]:=true;
end;
procedure getac;
var
i,head,tail,x:longint;
dl:array[0..26*maxn]of longint;
ic:char;
begin
head:=1;
tail:=1;
dl[1]:=0;
while head<=tail do
begin
x:=dl[head];
for ic:='A' to 'Z' do
if trie[x,ic]>0 then
begin
inc(tail);
dl[tail]:=trie[x,ic];
if x<>0 then fail[trie[x,ic]]:=trie[fail[x],ic];
end
else if x<>0 then trie[x,ic]:=trie[fail[x],ic];
place[x]:=place[x]or place[fail[x]];
inc(head);
end;
end;
procedure solve;
var
i,j,all:longint;
ic:char;
begin
f[0,0]:=1;
for i:=1 to m do
for j:=0 to tot do
if place[j]=false then
for ic:='A' to 'Z' do
f[i,trie[j,ic]]:=(f[i,trie[j,ic]]+f[i-1,j])mod md;
ans:=0;
for i:=0 to tot do
if place[i]=false then ans:=(ans+f[m,i])mod md;
all:=1;
for i:=1 to m do all:=(all*26) mod md;
writeln((all-ans+md)mod md);
end;
begin
tot:=0;
fillchar(place,sizeof(place),false);
fillchar(trie,sizeof(trie),0);
fail[0]:=-1;
readln(n,m);
for i:=1 to n do
begin
readln(s);
ins(s);
end;
getac;
solve;
end.