传送门:​​点击打开链接​

题意:DNA只有AGCT四种脱氧核糖核苷酸组成,现在告诉你n条致病基因序列,,问长度为m的DNA序列里不含任何的致病基因的种类数是多少。

思路:这题可谓是AC自动机的经典神题。。如果只是简单的认为AC自动机只不过是在文中匹配字符串那就打错特错了,它还可以用来压缩状态~

这题我们先构想一下动态规划。

如果我们没学过AC自动机,,现在假如致病基因的长度都为3.我们可能会这样做。

设dp[i][j][k][l]表示为长度为i,最后是j,k,l结尾的字符串有多少种。那么我们可以列出转移方程。。

这样做法是最朴素的,会存在一个问题,如果致病基因比较长的时候,那就要设很多维的状态才行,所以对于这道题来说不合适。


假如我们已经用AC自动机,将所有的致病基因添加到自动机上了,现在的总节点数为s

那么,其实,任何字符串的末尾都能通过这s种状态表示出来!!如果字符串的后缀是和致病基因的前缀相等,那么此时致病基因匹配的前缀的结尾必然会有一个节点,这个节点就能表示出这个状态!那么如果字符串的后缀都没有致病基因与其配对呢,那就用根节点来表示,表示现在的字符串后缀没有与任何致病基因前缀匹配。这样,,我们就利用了AC自动机压缩完了所有的状态,只要再构造出邻接矩阵,就可以利用快速矩阵幂算出答案了。


邻接矩阵如何构造呢?直接看AC自动机的Next数组就可以了,看它的子节点是哪些,只要子节点的End等于0,就说明这个子节点不是叶子,不是单词的结尾,那么就能转移。

这里还存在一个wa点,如果End[Fail[u]]不等于0,说明对于u来说,u之前的那一条链和Fail[u]所在的Fail[u]之前的那一条链是相同的,但是在Fail[u]有结尾单词,所以u这个位置其实也是一个单词的结尾位置,我们可以手动加上标志End[u]=1。

#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<cstdio>
#include<cctype>
#include<string>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
#define fuck(x) cout<<"["<<x<<"]"
#define FIN freopen("input.txt","r",stdin)
#define FOUT freopen("output.txt","w+",stdout)
using namespace std;
typedef long long LL;

/*MX为总长度*/
const int MX = 200 + 5;
const int mod = 100000;
const int matMX = 150 + 5;

struct Mat {
int m, n;
LL S[matMX][matMX];
Mat(int a, int b) {
m = a;
n = b;
memset(S, 0, sizeof(S));
}
Mat(int a, int b, LL w[][matMX]) {
m = a;
n = b;
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
S[i][j] = w[i][j];
}
}
}
};

Mat mat_mul(Mat &A, Mat &B) {
Mat C(A.m, B.n);
for(int i = 0; i < A.m; i++) {
for(int j = 0; j < B.n; j++) {
for(int k = 0; k < A.n; k++) {
C.S[i][j] = (C.S[i][j] + A.S[i][k] * B.S[k][j]) % mod;
}
}
}
return C;
}

Mat Blank(int m, int n) {
Mat ret(m, n);
for(int i = 0; i < m; i++) {
ret.S[i][i] = 1;
}
return ret;
}

Mat mat_pow(Mat &A, LL b) {
Mat ret = Blank(A.m, A.n);
while(b) {
if(b & 1) ret = mat_mul(ret, A);
A = mat_mul(A, A);
b >>= 1;
}
return ret;
}

struct AC_machine {
int rear, root;
int Next[MX][4], Fail[MX], End[MX];

void Init() {
rear = 0;
root = New();
}

int New() {
rear++;
End[rear] = 0;
for(int i = 0; i < 4; i++) {
Next[rear][i] = -1;
}
return rear;
}

inline int ID(char x) {
if(x == 'A') return 0;
if(x == 'G') return 1;
if(x == 'C') return 2;
return 3;
}

void Add(char *A) {
int n = strlen(A), now = root;
for(int i = 0; i < n; i++) {
int id = ID(A[i]);
if(Next[now][id] == -1) {
Next[now][id] = New();
}
now = Next[now][id];
}
End[now]++;
}

void Build() {
queue<int>Q;
Fail[root] = root;
for(int i = 0; i < 4; 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 u = Q.front(); Q.pop();

if(End[Fail[u]]) End[u] = 1;
for(int i = 0; i < 4; i++) {
if(Next[u][i] == -1) {
Next[u][i] = Next[Fail[u]][i];
} else {
Fail[Next[u][i]] = Next[Fail[u]][i];
Q.push(Next[u][i]);
}
}
}
}

Mat Query() {
Mat A(rear, rear);
for(int i = 1; i <= rear; i++) {
for(int j = 0; j < 4; j++) {
int chd = Next[i][j];
if(End[chd] == 0) A.S[i - 1][chd - 1]++;
}
}
return A;
}
} AC;

char word[MX];

int main() {
int m, n; //FIN;
while(~scanf("%d%d", &m, &n)) {
AC.Init();
for(int i = 1; i <= m; i++) {
scanf("%s", word);
AC.Add(word);
}
AC.Build();

Mat mat = AC.Query();
Mat ret = mat_pow(mat, n);

LL ans = 0;
for(int i = 0; i < ret.m; i++) {
ans = (ans + ret.S[0][i]) % mod;
}
printf("%I64d\n", ans);
}
return 0;
}