题目大意
给定 m m m个字符串,求长度为 n n n的、给定的 m m m个字符串不是其子串的字符串的种类数量。
做法
对 m m m个字符串建立一个AC自动机。此时我们考虑这个AC自动机中fail指针的含义,就是失配后通过特定字母可以到达的结点位置。
那么,整个AC自动机上,基于trie树的节点之间的父子关系+fail指针构成了节点与节点之间的移动路线。
我们从AC自动机的 0 0 0号点(trie树的根)出发,每走一步就相当于增加了一个字母,那么最后就是看从 0 0 0开始,走 n n n步到达所有可以到达的点的方案数。
所谓可以到达的点,就是可以走到的节点。换言之,如果走到这个节点凑出了 m m m个字符串之一,那么就是不合法节点,即不可到达的点。反过来就是可以到达的。
什么样的点是不可到达的?如上所说,凑出了
m
m
m个字符串之一,即关键词的最后一个字母所代表的节点,一定是不可到达的。
根据fail指针的定义,我们也可以发现,如果一个节点可以通过fail指针失配到一个不可到达的节点,那么这个节点也一定是不可到达的。
统计出不可到达的点之后,我们来看每一个点走一步之后可以到达哪些位置。
一般这种题目AC自动机的节点数量会非常少,所以我们可以用二维数组 c n t cnt cnt存储节点与节点之间走一步能否达到的关系。我们遍历每一个可以达到的节点 u u u,如果他可以去另外一个可以达到的节点 v v v,那么我们令 c n t [ u ] [ v ] + + cnt[u][v]++ cnt[u][v]++。
全部统计完后,我们就获得了走一步之后,第 i i i个节点能否到第 j j j个节点了。将其封装成一个矩阵 A A A。如果我们要走 n n n步,那么就求出矩阵 A A A的 n n n次幂,然后求最终结果矩阵 B B B中对于所有 i ∈ [ 0 , t o t ] i∈[0,tot] i∈[0,tot](tot是AC自动机中的点数)中 B [ 0 ] [ i ] B[0][i] B[0][i]的和。这个和就是题目的答案,即长度为 n n n的字符串中不包含 m m m个关键词的种类个数。
即:假设
A = [ a 0 , 0 a 0 , 1 ⋯ a 0 , t o t a 1 , 0 a 1 , 1 ⋯ a 1 , t o t ⋮ ⋮ ⋱ ⋮ a t o t , 0 a t o t , 1 ⋯ a t o t , t o t ] A= \left[ \begin{matrix} a_{0,0} & a_{0,1} & \cdots & a_{0,tot} \\ a_{1,0} & a_{1,1} & \cdots & a_{1,tot} \\ \vdots & \vdots & \ddots & \vdots \\ a_{tot,0} & a_{tot,1} & \cdots & a_{tot,tot} \end{matrix} \right] A=⎣⎢⎢⎢⎡a0,0a1,0⋮atot,0a0,1a1,1⋮atot,1⋯⋯⋱⋯a0,tota1,tot⋮atot,tot⎦⎥⎥⎥⎤
那么答案 a n s = a 0 , 0 + a 0 , 1 + a 0 , 2 + ⋯ + a 0 , t o t ans=a_{0,0}+a_{0,1}+a_{0,2}+\cdots +a_{0,tot} ans=a0,0+a0,1+a0,2+⋯+a0,tot,表示走 n n n步后从 0 0 0点走到各个点的方案数之和。
一般情况下 n n n会非常大,大约是 2 31 2^{31} 231的数量级,所以必须要用矩阵快速幂来加速矩阵运算。
POJ 2778 - DNA Sequence
和上面的题意完全一致。(或者说上面的题意是照着这个题目写的)
题目链接
传递的字母只有4个。
Code:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<queue>
using namespace std;
typedef long long LL;
const LL p = 100000;
const int N = 15;
const int matSize = 150;
int bad[N * 15];
int getID(char c) {
if (c == 'A') return 0;
else if (c == 'T') return 1;
else if (c == 'C') return 2;
else return 3;
}
int tot;
int tr[N * 15][5], fail[N * 15], idx[N * 15], val[N * 15], cnt[N];
struct AC_Automaton {
void init() {
tot = 0;
memset(bad, 0, sizeof(bad));
}
void insert(char *s, int id) {
int u = 0;
for (int i = 1; s[i]; ++i) {
if (!tr[u][getID(s[i])]) tr[u][getID(s[i])] = ++tot;
u = tr[u][getID(s[i])];
}
idx[u] = id; bad[u] = 1;
}
queue<int> q;
void build() {
for (int i = 0; i < 4; ++i) {
if (tr[0][i]) q.push(tr[0][i]);
}
while (q.size()) {
int u = q.front();
q.pop();
for (int i = 0; i < 4; ++i) {
if (tr[u][i]) {
fail[tr[u][i]] = tr[fail[u]][i];
bad[u] |= bad[fail[u]];
q.push(tr[u][i]);
}
else tr[u][i] = tr[fail[u]][i];
}
}
}
int query(char *t) {
int u = 0, res = 0;
for (int i = 1; t[i]; ++i) {
u = tr[u][getID(t[i])];
for (int j = u; j; j = fail[j]) val[j]++;
}
for (int i = 0; i <= tot; ++i) {
if (idx[i]) {
res = max(res, val[i]);
cnt[idx[i]] = val[i];
}
}
return res;
}
}ac;
struct Matrix {
int matA[150][150];
inline Matrix() { memset(matA, 0, sizeof(matA)); }
inline Matrix operator - (const Matrix &MAT_T) const {
Matrix MAT_RES;
for (int i = 0; i < matSize; ++i) for (int j = 0; j < matSize; ++j)
MAT_RES.matA[i][j] = (matA[i][j] - MAT_T.matA[i][j]) % p;
return MAT_RES;
}
inline Matrix operator + (const Matrix &MAT_T) const {
Matrix MAT_RES;
for (int i = 0; i < matSize; ++i) for (int j = 0; j < matSize; ++j)
MAT_RES.matA[i][j] = (matA[i][j] + MAT_T.matA[i][j]) % p;
return MAT_RES;
}
inline Matrix operator * (const Matrix &MAT_T) const {
Matrix MAT_RES;
for (int i = 0; i <= tot; ++i) {
for (int j = 0; j <= tot; ++j) {
MAT_RES.matA[i][j] = 0;
LL res = 0;
for (int k = 0; k <= tot; ++k) {
res += ((LL)matA[i][k] * MAT_T.matA[k][j]);
}
MAT_RES.matA[i][j] = res % p;
}
}
return MAT_RES;
}
inline Matrix operator ^ (LL MAT_X) const {
Matrix MAT_RES, MAT_BAS;
for (int i = 0; i < matSize; ++i) MAT_RES.matA[i][i] = 1;
for (int i = 0; i < matSize; ++i) for (int j = 0; j < matSize; ++j)
MAT_BAS.matA[i][j] = matA[i][j];
while (MAT_X) {
if (MAT_X&1ll)MAT_RES=MAT_RES*MAT_BAS;
MAT_BAS=MAT_BAS*MAT_BAS; MAT_X>>=1;
}
return MAT_RES;
}
};
int n, m;
char s[15];
Matrix Power(Matrix &A, int a) {
Matrix ans;
for (int i = 0; i <= tot; ++i) {
ans.matA[i][i] = 1;
}
while (a) {
if (a & 1) ans = ans * A;
A = A * A;
a >>= 1;
}
return ans;
}
int main() {
scanf("%d%d", &m, &n);
ac.init();
for (int i = 1; i <= m; ++i) {
scanf("%s", s + 1);
ac.insert(s, i);
}
ac.build();
Matrix A;
for (int i = 0; i <= tot; ++i) {
if (bad[i]) continue;
for (int j = 0; j <= 3; ++j) {
int nxt = tr[i][j];
if (!bad[nxt]) ++A.matA[i][nxt];
}
}
A = Power(A, n);
LL ans = 0;
for (int i = 0; i <= tot; ++i) {
ans = (ans + A.matA[0][i]);
}
printf("%d", ans % p);
return 0;
}
HDU 2243 - 考试路茫茫——单词情结
这个题和上一个题刚好反过来,是求长度不超过 n n n的所有串里包含 m m m个关键词中任意一个或多个的串的种类数量。
解法
既然是反过来的题目,那我们也反过来解。我们会求不包含这些词的串的数量,那我们就求所有可能的串的种数-不包含这些词的串的数量,就是我们要求的答案。
所有可能的串的种数非常好求。长度为 1 1 1的串的数量是 26 26 26,长度为 2 2 2的串的数量是 2 6 2 26^2 262,长度为 3 3 3的串的数量是 2 6 3 26^3 263……
所以所有可能的串的总数 S = 2 6 1 + 2 6 2 + ⋯ + 2 6 n S=26^1+26^2+\cdots+26^n S=261+262+⋯+26n。
但是这道题目 n n n非常大,是 2 31 2^{31} 231级别的,所以 O ( n ) O(n) O(n)是不行的。我们考虑用矩阵来求。我们可以列出以下矩阵乘法式:
[ 26 1 0 1 ] n [ 1 1 ] = [ S + 1 1 ] \left[ \begin{matrix} 26 & 1 \\ 0 & 1 \end{matrix} \right]^n \left[ \begin{matrix} 1 \\ 1 \end{matrix} \right] = \left[ \begin{matrix} S+1 \\ 1 \end{matrix} \right] [26011]n[11]=[S+11]
这样的话利用矩阵快速幂就可以在 O ( log n ) O(\log n) O(logn)的时间复杂度里求出 S S S。
现在来求长度不超过
n
n
n的不包含
m
m
m个关键词的串的种数。我们现在会求长度固定的串的种数。假设我们获得的移动一位的矩阵是
A
A
A。如果定长的话,我们令
B
=
A
n
B=A^n
B=An,如果
B
=
[
b
0
,
0
b
0
,
1
⋯
b
0
,
t
o
t
b
1
,
0
b
1
,
1
⋯
b
1
,
t
o
t
⋮
⋮
⋱
⋮
b
t
o
t
,
0
b
t
o
t
,
1
⋯
b
t
o
t
,
t
o
t
]
B= \left[ \begin{matrix} b_{0,0} & b_{0,1} & \cdots & b_{0,tot} \\ b_{1,0} & b_{1,1} & \cdots & b_{1,tot} \\ \vdots & \vdots & \ddots & \vdots \\ b_{tot,0} & b_{tot,1} & \cdots & b_{tot,tot} \end{matrix} \right]
B=⎣⎢⎢⎢⎡b0,0b1,0⋮btot,0b0,1b1,1⋮btot,1⋯⋯⋱⋯b0,totb1,tot⋮btot,tot⎦⎥⎥⎥⎤
那么对于长度为 n n n的串的种类数量 a n s n = b 0 , 0 + b 0 , 1 + ⋯ + b 0 , t o t ans_n=b_{0,0}+b_{0,1}+\cdots+b_{0,tot} ansn=b0,0+b0,1+⋯+b0,tot。
我们现在要求的是 a n s 1 + a n s 2 + ⋯ + a n s n ans_1+ans_2+\cdots+ans_n ans1+ans2+⋯+ansn。
我们不能 O ( n ) O(n) O(n)求,那么就需要借助矩阵来求。这里有一个小技巧,我们只需要把整个矩阵尺寸放大一圈,外围都用 0 0 0和 1 1 1填充,像这个样子——
A = [ a 0 , 0 a 0 , 1 ⋯ a 0 , t o t 1 a 1 , 0 a 1 , 1 ⋯ a 1 , t o t 1 ⋮ ⋮ ⋱ ⋮ ⋮ a t o t , 0 a t o t , 1 ⋯ a t o t , t o t 1 0 0 ⋯ 0 1 ] A= \left[ \begin{matrix} a_{0,0} & a_{0,1} & \cdots & a_{0,tot} & 1 \\ a_{1,0} & a_{1,1} & \cdots & a_{1,tot} & 1 \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ a_{tot,0} & a_{tot,1} & \cdots & a_{tot,tot} & 1 \\ 0 & 0 & \cdots & 0 & 1 \end{matrix} \right] A=⎣⎢⎢⎢⎢⎢⎡a0,0a1,0⋮atot,00a0,1a1,1⋮atot,10⋯⋯⋱⋯⋯a0,tota1,tot⋮atot,tot011⋮11⎦⎥⎥⎥⎥⎥⎤
我们试着来给他乘方,令 A i A^i Ai的右上角元素为 x i x_i xi,我们惊奇地发现,有
x i = 1 + a n s 1 + ⋯ + a n s i − 1 x_i=1+ans_1+\cdots + ans_{i-1} xi=1+ans1+⋯+ansi−1
那么有
x n = 1 + a n s 1 + ⋯ + a n s n − 1 x_n=1+ans_1+\cdots+ans_{n-1} xn=1+ans1+⋯+ansn−1
如果 B = A n B=A^n B=An,那么我们可以通过 B B B求得 a n s n ans_n ansn:
a n s n = b 0 , 0 + b 0 , 1 + ⋯ + b 0 , t o t ans_n=b_{0,0}+b_{0,1}+\cdots+b_{0,tot} ansn=b0,0+b0,1+⋯+b0,tot
而 x n = b 0 , t o t + 1 x_n=b_{0,tot+1} xn=b0,tot+1, a n s 0 = 1 ans_0=1 ans0=1,所以我们长度不超过 n n n的所有不包含给出的字符串的串的种数 a n s ans ans:
a n s = b 0 , 0 + b 0 , 1 + ⋯ + b 0 , t o t + 1 − 1 ans=b_{0,0}+b_{0,1}+\cdots+b_{0,tot+1}-1 ans=b0,0+b0,1+⋯+b0,tot+1−1
那么包含给出的 m m m个字符串中的一个或多个的字符串的种数就是 S − a n s S-ans S−ans。
注意unsigned long long输出。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long LL;
const LL matSize = 50;
const LL N = 8;
LL n, m;
LL tot;
LL tr[N * 10][26], fail[N * 10], bad[N * 10], val[N * 10], cnt[N];
struct AC_Automaton {
void init() {
memset(fail, 0, sizeof(fail));
memset(tr, 0, sizeof(tr));
memset(val, 0, sizeof(val));
memset(cnt, 0, sizeof(cnt));
memset(bad, 0, sizeof(bad));
tot = 0;
}
void insert(char *s, int id) {
int u = 0;
for (int i = 1; s[i]; ++i) {
if (!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot;
u = tr[u][s[i] - 'a'];
}
bad[u] = 1;
}
queue<int> q;
void build() {
for (int i = 0; i < 26; ++i) {
if (tr[0][i]) q.push(tr[0][i]);
}
while (q.size()) {
int u = q.front();
q.pop();
for (int i = 0; i < 26; ++i) {
if (tr[u][i]) {
fail[tr[u][i]] = tr[fail[u]][i];
bad[u] |= bad[fail[u]];
q.push(tr[u][i]);
}
else tr[u][i] = tr[fail[u]][i];
}
}
}
}ac;
struct Matrix
{
LL ms = matSize;
LL matA[matSize][matSize];
inline Matrix() { memset(matA, 0, sizeof(matA)); }
inline Matrix operator - (const Matrix &MAT_T) const {
Matrix MAT_RES;
for (int i = 0; i < ms; ++i) for (int j = 0; j < ms; ++j)
MAT_RES.matA[i][j] = (matA[i][j] - MAT_T.matA[i][j]);
return MAT_RES;
}
inline Matrix operator + (const Matrix &MAT_T) const {
Matrix MAT_RES;
for (int i = 0; i < ms; ++i) for (int j = 0; j < ms; ++j)
MAT_RES.matA[i][j] = (matA[i][j] + MAT_T.matA[i][j]);
return MAT_RES;
}
inline Matrix operator * (const Matrix &MAT_T) const {
Matrix MAT_RES; LL MAT_R;
for (int i = 0; i < ms; ++i) for (int k = 0; k < ms; ++k){
MAT_R = matA[i][k];
for (int j = 0; j < ms; ++j)
MAT_RES.matA[i][j]+=(MAT_T.matA[k][j]*MAT_R);
}
return MAT_RES;
}
inline Matrix operator ^ (LL MAT_X) const {
Matrix MAT_RES, MAT_BAS;
for (int i = 0; i < ms; ++i) MAT_RES.matA[i][i] = 1;
for (int i = 0; i < ms; ++i) for (int j = 0; j < ms; ++j)
MAT_BAS.matA[i][j] = matA[i][j];
while (MAT_X) {
if (MAT_X&1)MAT_RES=MAT_RES*MAT_BAS;
MAT_BAS=MAT_BAS*MAT_BAS; MAT_X>>=1;
}
return MAT_RES;
}
}ts;
char x[300];
LL S;
void main2() {
ac.init();
for (int i = 1; i <= n; ++i) {
scanf("%s", x + 1);
ac.insert(x, i);
}
ac.build();
Matrix ans; ans.ms = 2;
ans.matA[0][0] = ans.matA[1][0] = 1;
LL res = 0;
for (int i = 1; i <= 8; ++i) {
LL tmp = 1;
for (int j = 1; j <= i; ++j) {
tmp *= 26;
}
res += tmp;
}
ans = (ts ^ m) * ans;
S = ans.matA[0][0];
Matrix A; A.ms = tot + 3;
for (int i = 0; i <= tot; ++i) {
if (bad[i]) continue;
for (int j = 0; j < 26; ++j) {
if (!bad[tr[i][j]]) {
++A.matA[i][tr[i][j]];
}
}
}
for (int i = 0; i <= tot + 1; ++i) {
A.matA[i][tot + 1] = 1;
}
A = A ^ m;
LL fix = 0;
for (int i = 0; i <= tot + 1; ++i) {
fix += A.matA[0][i];
}
printf("%I64u\n", S - fix);
}
int main() {
ts.ms = 2;
ts.matA[0][0] = 26; ts.matA[0][1] = 1;
ts.matA[1][0] = 0; ts.matA[1][1] = 1;
while (~scanf("%lld%lld", &n, &m)) {
main2();
}
return 0;
}