题意:
给n个单词,要你构造长度为k的密文,使得密文解密后是由这n个单词构成的
解密书,有k页,一开始在第1页,每页26行。第i行一个字符a和一个数字b表示密文i解码后变成a,并翻到第b页
并且构造的密文中的子串不能出现给出的单词。
问k=1,2,3…m时候的方案数
n <= 50, 单词总长度 <= 50, m <= 2000
给出的单词不会出现重复,也不会有一个是另一个的前缀。单词可以重复在明文中出现。
解题思路:
看一下限制条件:
- 长度要为k
- 密文中不可以出现任何子串为某个单词
- 明文是由给出的单词构成
- 密码书翻到的页数不同,密文对应的明文不同
对于第二个限制,相当于建立一棵AC自动机之后,拿密文在AC自动机上跑,不会跑到某个后缀为给出的单词的结点上去。
对于第三个限制,相当于用解密出来的明文在字典树上跑,每次跑的都是字典树上存在的边,并且走到某个单词的结尾就跳回根,最终停留在根上。
看到单词总长度<=50,意味着建树之后结点数不会超过50,所以用:
d
p
(
i
,
j
,
k
,
l
)
dp(i,j,k,l)
dp(i,j,k,l)来表示当前长度为i,当前密文在ac自动机上走到结点j,明文在字典树上走到k,密码书翻到了第l页的方案数。
转移显然。利用滚动数组降低空间复杂度为
O
(
n
∣
S
∣
2
)
O(n|S|^2)
O(n∣S∣2),时间复杂度为
O
(
m
n
∣
S
∣
2
)
O(mn|S|^2)
O(mn∣S∣2)
考试的时候忘记调用build获取fail指针的我是笨蛋。
赛后调用build之后本地测试25个测试用例均在1s内得到正确答案。
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
const int maxn = 105;
int ch[maxn][26], ed[maxn], fail[maxn], nxt[maxn][26], val[maxn];
int n,m;
#define P pair<int,int>
int g[55][26];//i th book got j,ned to full
int to[55][26];
char cmd[maxn];
int rt, tot;
void ins(char *s){
int p = rt;
while(*s){
int x = *s - 'a';
if(!ch[p][x]){
ch[p][x] = ++tot;
nxt[p][x] = tot;
}
p = ch[p][x];
s++;
}
ed[p] = 1;
val[p] = 1;
}
queue<int> q;
void build(){
while(q.size()) q.pop();
fail[rt] = rt;
for(int i = 0; i < 26; ++i){
if(nxt[rt][i]){
fail[ch[rt][i]] = rt;
q.push(ch[rt][i]);
}
}
while(q.size()){
int u = q.front(); q.pop();
for(int i = 0; i < 26; ++i){
if(!nxt[u][i]){
nxt[u][i] = nxt[fail[u]][i];
}else{
fail[nxt[u][i]] = nxt[fail[u]][i];
ed[nxt[u][i]] |= ed[nxt[fail[u]][i]];
q.push(nxt[u][i]);
}
}
}
return;
}
int dp[2][55][55][55];//len is i, ac is j, tree is k, book is l
int main(){
scanf("%d%d", &n, &m);
for(int i = 0; i < 26; ++i){
for(int k = 1; k <= n; ++k){
char s[5];
int x = 0;
scanf("%s",s);
int ch = s[0] - 'a';
int len = strlen(s);
for(int j = 1; j < len; ++j) x = x*10 + s[j] - '0';
g[k][i] = ch;
to[k][i] = x;
}
}
rt = tot = 0;
while(scanf( "%s", cmd)!=EOF){
ins(cmd);
//cout<<cmd<<endl;
}
build();//考试的时候忘记build了...
//cout<<ch[0][0]<<endl;
dp[0][0][0][1] = 1;
int cur = 0, w = 1;
for(int i = 0; i < m; ++i){
int ans = 0;
for(int j = 0; j <= tot; ++j){
for(int k = 0; k <= tot; ++k){
for(int l = 1; l <= n; ++l){
if(dp[cur][j][k][l] == 0) continue;
//cout<<"i:"<<i <<" j:"<<j<<" k:"<<k<<" l:"<<l<<" dp:"<<dp[cur][j][k][l]<<endl;
for(int x = 0; x < 26; ++x){
if(ed[nxt[j][x]]) continue;
int v = g[l][x];
//cout<<"x:"<<x<<" v:"<<v<<endl;
if(ch[k][v] == 0) continue;
//cout<<"j:"<<j<<" x:"<<x<<endl;
//cout<<"x:"<<char(v+'a')<<endl;
if(val[ch[k][v]] == 0){
dp[w][nxt[j][x]][ch[k][v]][to[l][x]] = (dp[w][nxt[j][x]][ch[k][v]][to[l][x]] + dp[cur][j][k][l])%mod;
//cout<<"x: "<<(char)(x+'a')<<" j:"<<nxt[j][x]<<" k:"<<ch[k][v]<<" l:"<<to[l][x]<<endl;
}
else {
dp[w][nxt[j][x]][0][to[l][x]] = (dp[w][nxt[j][x]][0][to[l][x]] + dp[cur][j][k][l])%mod;
//cout<<"x: "<<(char)(x+'a')<<" j:"<<nxt[j][x]<<" k:"<<0<<" l:"<<to[l][x]<<endl;
ans = (ans + dp[cur][j][k][l])%mod;
}
}
dp[cur][j][k][l] = 0;
}
}
}
swap(cur, w);
printf("%d\n", ans);
//if(ans != Ans[i+1]){printf("Wa!");return 0;}
}
//printf("Ac!\n");
return 0;
}