题目描述
背单词,始终是复习英语的重要环节。在荒废了3年大学生涯后,Lele也终于要开始背单词了。
一天,Lele在某本单词书上看到了一个根据词根来背单词的方法。比如"ab",放在单词前一般表示"相反,变坏,离去"等。
于是Lele想,如果背了N个词根,那这些词根到底会不会在单词里出现呢。更确切的描述是:长度不超过L,只由小写字母组成的,至少包含一个词根的单词,一共可能有多少个呢?这里就不考虑单词是否有实际意义。
比如一共有2个词根 aa 和 ab ,则可能存在104个长度不超过3的单词,分别为
(2个) aa,ab,
(26个)aaa,aab,aac…aaz,
(26个)aba,abb,abc…abz,
(25个)baa,caa,daa…zaa,
(25个)bab,cab,dab…zab。
这个只是很小的情况。而对于其他复杂点的情况,Lele实在是数不出来了,现在就请你帮帮他。
输入
本题目包含多组数据,请处理到文件结束。
每组数据占两行。
第一行有两个正整数N和L。(0<N<6,0<L<2^31)
第二行有N个词根,每个词根仅由小写字母组成,长度不超过5。两个词根中间用一个空格分隔开。
输出
对于每组数据,请在一行里输出一共可能的单词数目。
由于结果可能非常巨大,你只需要输出单词总数模2^64的值。
样例
2 3
aa ab
1 2
a
104
52
思路
又是一个AC自动机的好题。
本质上是bzoj1030和poj2778的综合,只不过数据范围更加大,更加麻烦一些。
摘自poj2778的题解
首先,倘若这个问题中要组成的字符串的长度m比较小的话,题目的做法就类似bzoj1030,我们设dp[i][j]为当前长度为i的字符串处于Trie树中的第j号结点所具有的方案数,就可以通过转移方程dp[i][next[j][k]+=dp[i-1][j]求解。
但是在这个问题中,字符串的长度多达2e9,显然在这个长度下甚至连一个dp数组都开不下,显然用dp去做显然不太现实。因此我们考虑从图的角度审视这个问题。
我们分析之前所构造出的转移方程,dp[i+1][j]+=dp[i][next[j][k]],我们发现这个转移方程在Trie图上的表示为:结点j能够到达结点next[j][k],即两个结点在Trie图上是可达的。
而我们题目中所要求的长度为n的方案数,实质上等价于要求我们从Trie图的根节点开始转移,一直转移n次,且保证转移到的结点不是结尾字符所得到的方案数。
首先如果数据范围较小,我们直接可以用一个dp去求出不符合条件的方案数,用总方案数-不合法的方案数即为答案。
但是在这个题中数据范围极大,因此我们必须用类似poj2778中的构造Trie图跑矩阵快速幂(见上)的方法去做。
而因为题目中让我们求的是至少包含1个模式串的方案数,因此我们需要求出 ∑ k = 1 m A k \sum_{k=1}^{m}A^k ∑k=1mAk(A为邻接矩阵)。因此我们只需要在基础的构建矩阵的过程中使矩阵多开一维,使得第i+1列全为1作为求和。
另外,因为要求至少包含一个模式串的方案数,因此在这种情况下,总方案数为:
26
+
2
6
2
+
2
6
3
+
.
.
.
+
2
6
m
26+26^2+26^3+...+26^m
26+262+263+...+26m。
显然这个式子直接去快速幂去求必定也会超时,因此我们也得对这个式子进行矩阵优化,对于上述式子有:
S
=
26
+
2
6
2
+
2
6
3
+
.
.
.
+
2
6
n
S=26+26^2+26^3+...+26^n
S=26+262+263+...+26n,设有
F
(
n
)
=
S
+
1
F(n)=S+1
F(n)=S+1,因此
F
(
n
)
F(n)
F(n)可转化成矩阵形式:
F
(
n
)
=
26
∗
F
(
n
−
1
)
+
1
F(n) = 26 * F(n-1) + 1
F(n)=26∗F(n−1)+1,此后再使用以此矩阵快速幂求解并让求出的两个结果相减即可。
-
ps:因为题目中要求我们对2^64取模,因此我们只需要将所有变量开成unsigned long long即可(溢出时会自动对2^64取模)。
-
注意build中的一个操作
end[now] |= end[fail[now]];
终点状态需要转移,比如有单词ase、sex,对于ase的e节点,它的x结点的end应当是1(e的fail明显是sex的e结点,而这个e结点的x结点end是1),因为asex单词包含了sex,也属于出现了’sex’的情况。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <string>
#include <queue>
using namespace std;
typedef unsigned long long LL;
const int maxn = 50+5;
int n, m;
char st[maxn];
struct Matrx{
LL v[maxn][maxn], n;
Matrx(){}
Matrx(int _n){
n = _n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
v[i][j] = 0;
}
}
};
Matrx mul(Matrx &a, Matrx &b){
Matrx res;
res = Matrx(a.n);
for(int i=0;i<a.n;i++)
{
for(int j=0;j<a.n;j++)
{
for(int k=0;k<a.n;k++)
res.v[i][j] += a.v[i][k] * b.v[k][j];
}
}
return res;
}
Matrx powMod(Matrx a, LL n)
{
Matrx res = Matrx(a.n);
for(int i=0;i<a.n;i++)
res.v[i][i] = 1;
while(n){
if(n&1) res = mul(res, a);
a = mul(a, a);
n>>=1;
}
return res;
}
struct Trie{
int next[500010][26], fail[500010], end[500010];
int root, L;
int newnode()
{
for(int i=0;i<26;i++)
next[L][i] = -1;
end[L] = 0;
return L++;
}
void Init()
{
L = 0;
root = newnode();
}
void insert(char buf[]){
int len = (int)strlen(buf);
int now = root;
for(int i=0;i<len;i++)
{
if(next[now][buf[i]-'a']==-1)
next[now][buf[i]-'a'] = newnode();
now = next[now][buf[i]-'a'];
}
end[now]++;
}
void build(){
queue<int> Q;
fail[root] = root;
for(int i=0;i<26;i++)
if(next[root][i]==-1)
next[root][i] = root;
else{
fail[next[root][i]] = root;
Q.push(next[root][i]);
}
while(!Q.empty()){
int now = Q.front();
Q.pop();
for(int i=0;i<26;i++)
if(next[now][i]==-1)
next[now][i] = next[fail[now]][i];
else{
fail[next[now][i]] = next[fail[now]][i];
Q.push(next[now][i]);
}
/********这里需要注意**********/
end[now] |= end[fail[now]];
/*****************************/
}
}
Matrx get_mat()
{
Matrx res = Matrx(L+1);
for(int i=0;i<L;i++)
{
for(int j=0;j<26;j++){
if(end[next[i][j]]) continue;
res.v[i][next[i][j]]++;
}
}
for(int i=0;i<L+1;i++){
res.v[i][L] = 1;
}
return res;
}
}ac;
int main()
{
while(~scanf("%d%d", &n, &m))
{
ac.Init();
for(int i=0;i<n;i++){
cin >> st;
ac.insert(st);
}
ac.build();
Matrx m1 = ac.get_mat();
LL res = 0;
m1 = powMod(m1, m);
for(int i=0;i<m1.n;i++){
res += m1.v[0][i]; // 从 根节点 到其他节点的不经过结尾的路径数
}
res--; // ? 应该是减去(0,0)位置的1(即根节点到根节点自己,是没有意义的)
Matrx m2 = Matrx(2);
m2.v[0][0] = 26;
m2.v[1][1] = m2.v[0][1] = 1;
m2 = powMod(m2, m);
LL ans = m2.v[0][0] + m2.v[0][1];
ans--; // F(n) = S+1, S = 26 + 26^2 + 26^3 + …… + 26^n, F(n) = F(n-1) + 1
cout << ans - res << endl;
}
return 0;
}