这是做的第一道在Trie图上跑dp的题
根据传说中的惯用套路
dp[i][j][0/1]表示以i号节点结尾走了j条边是否合法
对于这道题而言并不需要考虑合法状态,使用补集转化是更好的选择
总的方案mpow(26,m)
减去所有的sigma i=0->cnt 的dp[i][m]
就是最终答案
注意补全Trie图的部分有细节
贴代码走人
#include<bits/stdc++.h>
using namespace std;
const int Mod=12345;
char s[65][105];
int dp[105][6005];
bool danger[6005];
int n,m,root,cnt;
struct Node {
int fail;
int nxt[26];
}ac[12005];
void getroot() {
root=0;
ac[root].fail=root;
for (int i=0;i<=25;i++) {
ac[root].nxt[i]=root;
}
}
void insert(char *s) {
int len=strlen(s);
int now=root;
for (int i=0;i<len;i++) {
int id=s[i]-'A';
if (!ac[now].nxt[id]) {
ac[now].nxt[id] = ++cnt;
}
now=ac[now].nxt[id];
}
if (now!=root) {
danger[now]|=1;
}
}
int q[12005];
void getfail() {
int head=0,tail=0;
int now=root;
for (int i=0;i<=25;i++) {
if (ac[now].nxt[i]) {
q[tail++]=ac[now].nxt[i];
ac[ac[now].nxt[i]].fail=root;
}
}
while (head<tail) {
int u=q[head++];
for (int i=0;i<=25;i++) {
int son=ac[u].nxt[i];
int fafail=ac[u].fail;
if (son) {
ac[son].fail=ac[fafail].nxt[i];
q[tail++]=son;
} else {
ac[u].nxt[i]=ac[fafail].nxt[i];
}
}
danger[u]|=danger[ac[u].fail];
}
}
int main() {
freopen("text.in","r",stdin);
freopen("text.out","w",stdout);
scanf("%d%d",&n,&m);
getroot();
for (int i=1;i<=n;i++) {
scanf("%s",s[i]);
insert(s[i]);
}
getfail();
dp[0][root]=1;
for (int i=0;i<=m;i++) {
for (int j=0;j<=cnt;j++) {
if (danger[j]) continue;
for (int k=0;k<=25;k++) {
dp[i+1][ac[j].nxt[k]]=(dp[i+1][ac[j].nxt[k]]+dp[i][j])%Mod;
}
}
}
int ans=0;
for (int i=0;i<=cnt;i++) {
if (!danger[i]) {
ans=(ans+dp[m][i])%Mod;
}
}
int tmp=1;
for (int i=1;i<=m;i++) {
tmp=tmp*26%Mod;
}
printf("%d\n",((tmp-ans)%Mod+Mod)%Mod);
return 0;
}