Problem Description
背单词,始终是复习英语的重要环节。在荒废了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实在是数不出来了,现在就请你帮帮他。
一天,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实在是数不出来了,现在就请你帮帮他。
Input
本题目包含多组数据,请处理到文件结束。
每组数据占两行。
第一行有两个正整数N和L。(0<N<6,0<L<2^31)
第二行有N个词根,每个词根仅由小写字母组成,长度不超过5。两个词根中间用一个空格分隔开。
每组数据占两行。
第一行有两个正整数N和L。(0<N<6,0<L<2^31)
第二行有N个词根,每个词根仅由小写字母组成,长度不超过5。两个词根中间用一个空格分隔开。
Output
对于每组数据,请在一行里输出一共可能的单词数目。
由于结果可能非常巨大,你只需要输出单词总数模2^64的值。
由于结果可能非常巨大,你只需要输出单词总数模2^64的值。
Sample Input
2 3 aa ab 1 2 a
Sample Output
104 52
题解
此题是poj2778的升级。我们可以先求出不含词根的单词个数,再用总数减去即可。由于长度是小于等于L,所以矩阵A'=A^1 + A^2 + A^3 + A^4 + …… + A^L,总数=26 + 26^2 + 26^3 + 26^4 + 26^5 +……+26^L = (26^(L+1)-26)/25。所以不同点就是求出矩阵A'。其求法可用折半法来求(可参照poj3233)。
Ps:题中L<2^32,又由于要使用L+1,所以应用long long储存。至于取模则直接用unsigned long long 自然溢出即可。
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
//AC auto
const int MS = 33, Siz = 26;
int ch[MS][Siz], Fail[MS], root, totN;
bool is_R[MS];
int NewNode() {
is_R[++totN] = 0;
memset(ch[totN], 0, sizeof ch[totN]);
return totN;
}
void Insert(char *S) {
int cur = root, idx;
while (*S) {
idx = *S - 'a';
if (!ch[cur][idx]) ch[cur][idx] = NewNode();
cur = ch[cur][idx];
++S;
}
is_R[cur] = 1;
}
void BuildFail() {
queue<int>q;
Fail[root] = root;
for (int i = 0; i < Siz; ++i) if (ch[root][i]) {
Fail[ch[root][i]] = root;
q.push(ch[root][i]);
}
int p, f;
while (!q.empty()) {
p = q.front(); q.pop();
if (is_R[Fail[p]]) is_R[p] = 1;
for (int i = 0; i <Siz; ++i)
if (ch[p][i]) {
f = Fail[p];
while (!ch[f][i] && f) f = Fail[f];
if (ch[f][i]) Fail[ch[p][i]] = ch[f][i];
else Fail[ch[p][i]] = root;
q.push(ch[p][i]);
} else ch[p][i] = ch[Fail[p]][i];
}
}
//Matix
typedef unsigned long long LL;
struct Mat {
LL mp[MS][MS];
void clear() {
memset(mp, 0, sizeof mp);
}
Mat() { this->clear(); }
Mat operator + (const Mat &A) const {
Mat C;
for (int i = 0; i < MS; ++i)
for (int j = 0; j < MS; ++j)
C.mp[i][j] = mp[i][j] + A.mp[i][j];
return C;
}
Mat operator * (const Mat &A) const {
Mat C;
for (int i = 0; i < MS; ++i)
for (int j = 0; j < MS; ++j)
for (int k = 0; k < MS; ++k)
C.mp[i][j] += mp[i][k] * A.mp[k][j];
return C;
}
Mat operator ^ (const int &c) const {
int k = c;
Mat B = *this, C;
for (int i = 0; i < MS; ++i) C.mp[i][i] = 1;
while (k) {
if (k & 1) C = C * B;
B = B * B;
k >>= 1;
}
}
//求<span style="font-size:18px;">A^1 + A^2 + A^3 + A^4 + …… + A^L</span>
Mat operator << (const LL &c) const {
int i;
for (i = 0; (1 << i) <= c; ++i);
Mat C, B;
C = B = *this;
for (i -= 2; ~i; --i) {
C = C + C * B;
B = B * B;
if ((1 << i) & c) B = B * (*this), C = C + B;
}
return C;
}
};
void BuildMat(Mat &A) {
A.clear();
for (int i = 0; i <= totN; ++i)
for (int j = 0; j < Siz; ++j)
if (!is_R[i] && !is_R[ch[i][j]]) ++A.mp[i][ch[i][j]];
}
const LL inr = 10330176681277348905ULL;//25的逆元
char tmpS[10];
LL mod_pow(LL base, LL n) {
LL ans = 1;
while (n) {
if (n & 1) ans = ans * base;
base = base * base;
n >>= 1;
}
return ans;
}
int main() {
int N;
LL L,tot;
Mat ans;
while (scanf("%d", &N) == 1) {
cin >> L;
totN = -1; root = NewNode();
while (N--) {
scanf("%s", tmpS);
Insert(tmpS);
}
BuildFail();
BuildMat(ans);
ans = ans << L;
tot = mod_pow(26,(LL)L + 1) - 26;
tot = tot * inr;
for (int i = 0; i <= totN; ++i)
tot -= ans.mp[0][i];
cout << tot << endl;
}
return 0;
}