题目:
读入n(n <= 6)个字符串,每个字符串长度不超过50,且字符串都是由小写字母构成。读入一个整数L,L <= 50, 现在要求你构造一个长度是L的全部由小写字母构成的字符串X,而且要满足读入的n个字符串当中,恰好有C个字符串是你构造的字符串X的子串,那么你最多可以构造出多少个不同的X?答案模1000000009。
输入格式:
第一行,三个整数N,C,L。 0 <= C <= N。
接下来有N行,每行一个字符串。
输出格式:
一个整数
输入样例 :
4 2 3
a
aa
aaa
aaaa
输出样例:
50
样例解释: (合法的字符串的形式只能是:”Xaa” 或 “aaX”, 其中 X 是字符’b’-‘z’中的任意一个)
题解:
这题一看就是AC自动机+状压dp,难点在于两点:
一是状压的方程该如何推,首先一定以长度为阶段,每个子串是前缀树(Trie)中串的后缀,打标记则代表有完整串。方程通过给AC自动机节点编号每次贡献给儿子可以搞定,并不难想。
二是AC自动机该如何构图以满足转移方程的次序。我们知道每个点的信息不只是当前的标记信息,还要或上fail指针一路上去的信息(即其后缀的信息),所以我们在bfs求失败指针时可预处理好。有一个难点在于当前节点可以转移到其失败指针所指向的节点,但经我们观察发现这样转移并没有用,因为这是相反方向的转移,即本节点信息已经包含其fail了,但可转移到其儿子节点。于是我们为了简单起见,直接在构图时将空儿子节点指向其失败指针的儿子,没有就指向根(不能指向空,否则有些状态转移不到),注意这是一个递归的过程。然后我们直接在Trie图上进行状压dp就行了,dp部分比较简单,但构出Trie图以及处理指针指向需要好好思索。
时间复杂度O(L*Node*2^n*26+构图);
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MAXL 55
#define NN 55
#define N 7
#define MOD 1000000009
using namespace std;
int n, C, L, dp[NN][N*MAXL][(1<<N)+5];
int cur, cnt[(1<<N)+5], ans;
char x[MAXL];
struct Trie{
Trie *son[30], *fail;
int s, num;
void Clear(){
for(int i = 0; i < 26; i++) son[i] = NULL;
s = 0;
num = cur;
}
};
Trie *Root, *q[N*MAXL], Node[N*MAXL];
inline Trie *NewTnode(){
Node[cur].Clear();
return Node+cur++;
}
void AC_Insert(char *x, int id){
int len = strlen(x);
Trie *now = Root;
for(int i = 0; i < len; i++){
int pos = x[i] - 'a';
if(!now->son[pos]) now->son[pos] = NewTnode();
now = now->son[pos];
}
now->s = (1 << (id - 1));
}
void AC_Buildfail(){
Root->fail = NULL;
q[0] = Root;
int head = 0, tail = 0;
while(head <= tail){
Trie *now = q[head++];
for(int i = 0; i < 26; i++){
if(now->son[i]){
q[++tail] = now->son[i];
now->son[i]->fail = (now == Root) ? Root : now->fail->son[i];
now->son[i]->s |= now->son[i]->fail->s;
}
else now->son[i] = (now == Root) ? Root : now->fail->son[i];
}
}
}
void Count(){
for(int s = 0; s < (1<<n); s++){
int t = s;
while(t){
if(t & 1) cnt[s] ++;
t >>= 1;
}
}
}
int main(){
scanf("%d%d%d", &n, &C, &L);
Count();
Root = NewTnode();
for(int i = 1; i <= n; i++){
scanf("%s", &x);
AC_Insert(x, i);
}
AC_Buildfail();
memset(dp, 0, sizeof(dp));
dp[0][0][0] = 1;
for(int i = 0; i <= L; i++)
for(int j = 0; j < cur; j++)
for(int s = 0; s < (1<<n); s++){
if(!dp[i][j][s]) continue;
for(int k = 0; k < 26; k++){
int nj = Node[j].son[k]->num, ns = Node[j].son[k]->s;
dp[i+1][nj][s|ns] = (dp[i+1][nj][s|ns] + dp[i][j][s]) % MOD;
}
}
for(int j = 0; j < cur; j++)
for(int s = 0; s < (1<<n); s++)
if(cnt[s] == C) ans = (ans + dp[L][j][s]) % MOD;
printf("%d\n", ans);
return 0;
}