題目大意:給你一堆字符串,一個串不合法的條件是這些字符串中任意一個是這個串的子串,求合法的串的數量
其實這道題比 [HNOI2008]GT考試 那道題好寫一些,但道理是一樣的
只不過這道題的答案可以轉化爲 所有可能的字符串(26^m)數量 - 不合法的字符串數量
定義f[i][j]表示匹配到了第i個字符,現在在Trie樹上匹配到了第j個節點的方案數
GT考試是跳Next,每次找出 和 插入這個字符後形成的字符串 具有相同最長後綴的位置
那麼對於Trie圖來說,這不就是fail指針麼
Trie樹被補全成Trie樹後
如果在原來的Trie樹中某個節點x,它並沒有兒子ch[x][c],那麼在補全後,ch[x][c]自動指向的是x的fail指針指向的c兒子,即ch[fail[x]][c]
這不正是我們想要轉移的位置麼,非常智能
總結:字符串套KMP/AC自動機/Trie圖的題,通常都有f[i][j]表示文本串匹配到了第i位,模式串匹配到了第j位這類狀態,且有一些題可以用矩陣乘法優化。
#include <queue>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define N 6010
#define M 28
#define mod 10007
#define ui unsigned int
#define idx(x) (x-'A'+1)
#define inf 0x3f3f3f3f
using namespace std;
//re
int n,m;
char str[65][110];
int f[120][N];
int qpow(int x,int y){
int ans=1;
while(y){
if(y&1) ans=(ans*x)%mod;
x=(x*x)%mod,y>>=1;
}return ans;
}
struct Trie{
int ch[N][M],fa[N],fail[N],ed[N],tot;
void Build()
{
for(int i=1;i<=n;i++)
{
int len=strlen(str[i]+1),x=0;
for(int j=1;j<=len;j++)
{
int c=idx(str[i][j]);
if(!ch[x][c])
tot++,ch[x][c]=tot,fa[tot]=x;
x=ch[x][c];
if(j==len) ed[x]=1;
}
}
}
void Fail()
{
queue<int>q;
for(int i=1;i<=26;i++)
if(ch[0][i]) q.push(ch[0][i]);
while(!q.empty())
{
int x=q.front();q.pop();
for(int i=1;i<=26;i++)
if(ch[x][i])
fail[ch[x][i]]=ch[fail[x]][i],
q.push(ch[x][i]);
else
ch[x][i]=ch[fail[x]][i];
}
}
int solve()
{
f[0][0]=1;
queue<int>q;
for(int i=0;i<=m;i++)
for(int x=0;x<=tot;x++)
for(int c=1;c<=26;c++)
{
int flag=1;
for(int k=ch[x][c];k;k=fail[k])
if(ed[k]){flag=0;break;}
if(!flag) continue;
(f[i+1][ch[x][c]]+=f[i][x])%=mod;
}
int ans=0;
for(int x=0;x<=tot;x++)
if(!ed[x]) (ans+=f[m][x])%=mod;
return ans;
}
}t;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%s",str[i]+1);
t.Build();
t.Fail();
printf("%u\n",(qpow(26,m)-t.solve()+mod)%mod);
return 0;
}