传送门:点击打开链接
题意: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; }