GEN - Text Generator
题目链接:luogu SP1676 / SPOJ 1676
题目大意
给定n(n<=10)个字符串(长度<=6),要求构造一个长度为L(<=10^6)的字符串,至少包含n个字符串中任何一个,求方案数mod10007的值(所有字符都是大写字母)
思路
这道题其实就是文本生成器的升级版,没有做过的可以先做一做。
这里就默认你们已经知道了文本生成器的做法。
那我们观察一下它 dp 转移的式子,你就会发现你好像可以用矩阵乘法来做。
没错,你就建出转移矩阵,然后乘
L
L
L 个转移矩阵就可以了。
转移矩阵就是这个地方可以加上就标
1
1
1,否则就是
0
0
0。
但是这道题很毒瘤,这样搞会 TLE。
我们就要取模的时候在取模(就是小于零或者大于
10007
10007
10007),而且我们还可以缩小矩阵的大小。
一开始我们的矩阵就是 Trie 树上节点数量,那很明显,bool 值是真的点肯定都是
0
0
0,那我们就直接把这些点,重新编号,从而使得矩阵的大小变小。
代码
#include<queue>
#include<cstdio>
#include<cstring>
#define mo 10007
using namespace std;
struct Trie {
int son[26], fail;
bool cant;
}tree[66];
struct matrix {
int n, m;
int a[66][66];
}A, re, B;
int n, m, now, thi, size, KK, ans, newKK, dy[66];
queue <int> q;
char c[11];
matrix operator *(matrix x, matrix y) {
re.n = x.n;
re.m = y.m;
for (int i = 1; i <= re.n; i++)
for (int j = 1; j <= re.m; j++)
re.a[i][j] = 0;
for (int k = 1; k <= x.m; k++)
for (int i = 1; i <= re.n; i++)
for (int j = 1; j <= re.m; j++) {
re.a[i][j] += x.a[i][k] * y.a[k][j];
if (re.a[i][j] >= mo) re.a[i][j] %= mo;
}
return re;
}
void jzksm(int times) {
B = A;
times--;
while (times) {
if (times & 1) B = B * A;
A = A * A;
times >>= 1;
}
}
void build() {
now = 0;
for (int i = 0; i < size; i++) {
thi = c[i] - 'A';
if (!tree[now].son[thi]) tree[now].son[thi] = ++KK;
now = tree[now].son[thi];
}
tree[now].cant = 1;
}
void get_fail() {
for (int i = 0; i < 26; i++)
if (tree[0].son[i]) {
tree[tree[0].son[i]].fail = 0;
q.push(tree[0].son[i]);
}
while (!q.empty()) {
now = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
if (tree[now].son[i]) {
tree[tree[now].son[i]].fail = tree[tree[now].fail].son[i];
q.push(tree[now].son[i]);
tree[tree[now].son[i]].cant |= tree[tree[tree[now].son[i]].fail].cant;
}
else tree[now].son[i] = tree[tree[now].fail].son[i];
}
}
}
int ksm(int x, int y) {
int ree = 1;
while (y) {
if (y & 1) ree = (ree * x) % mo;
x = (x * x) % mo;
y >>= 1;
}
return ree;
}
int main() {
while (scanf("%d %d", &n, &m) != EOF) {
memset(A.a, 0, sizeof(A.a));
memset(tree, 0, sizeof(tree));
KK = 0;
newKK = 0;
for (int i = 1; i <= n; i++) {
scanf("%s", &c);
size = strlen(c);
build();
}
get_fail();
for (int i = 0 ; i <= KK; i++)
if (!tree[i].cant) dy[i] = ++newKK;//把能用的点找出来,以矩阵大小,减少时间
for (int i = 0; i <= KK; i++) {//构建转移矩阵
if (tree[i].cant) continue;
for (int j = 0; j < 26; j++)
if (!tree[tree[i].son[j]].cant)
A.a[dy[i]][dy[tree[i].son[j]]]++;
}
A.n = newKK;
A.m = newKK;
jzksm(m);
ans = ksm(26, m);
for (int i = 1; i <= newKK; i++) {
ans = ans - B.a[1][i];
if (ans < 0) ans += mo;
}
printf("%d\n", ans);
}
return 0;
}