POJ2778 DNA Sequence AC自动机上dp
网址:https://vjudge.net/problem/POJ-2778
题意:
给出字符集${A,C,G,T}$和一些字符串(长度不超过$10$,且数量不超过$10$个),求长度为$n(n \leq 2e9)$的字符串中不包括上面这些字符串的字符串的数量。
题解:
我们可以先考虑一种方式:设$dp(i,j)$是用了$i$个字符拼出符合题意的长度为$j$的字符串的数量,在本题中$dp(i,j)=\sum _{j' \subseteq j} dp(i-1,j')$,显然时间复杂度是指数级的,不可能通过题目。
因为本题是多模式匹配,所以显然使用$AC$自动机。$AC$自动机的结束结点就是不可到达结点,然后因为AC自动机上某个节点的$fail$结点表示这个字符串的前缀子串的后缀串的结点。所以如果它的$fail$结点不可达,那么这个结点一定不可达。然后我们把$AC$自动机变成一张用邻接矩阵表示的有向图,设图上结点数为$p$。
然后以下介绍两种常见的$dp$策略:
一、我们考虑$dp(i,j)$是从$Trie$的根走$i$步到编号为$j$的结点的方案数,则有$dp(i,j)=\sum _{!tr[k].flag} dp(i-1,k)$ $(tr[k].flag==true \leftrightarrow k$点不可达$)$,这时就可以在$n$比较小但是矩阵很大的时候以时间复杂度为$O(np^2)$计算出来。显然在这个题不太行。
二、我们考虑在有向图中求从$s$点出发$n$步是否能到达$t$点的求法是通过将矩阵乘$n$次求出来,这一过程可以使用矩阵快速幂加速。时间复杂度是$O(p^3logn)$,本题可以通过。
AC代码:
#include <cstdio> #include <cstring> #include <queue> #include <map> using namespace std; typedef long long ll; const int N = 105; const int mod = 100000; map<char, int>mp; int trie[N][4]; int fail[N]; bool f[N]; struct Mat { ll mat[N][N]; Mat() { memset(mat, 0, sizeof(mat)); } }; char s[15]; int cnt; void insert(char* s, int len) { int root = 0; for (int i = 0; i < len; ++i) { int nxt = mp[s[i]]; if (!trie[root][nxt]) trie[root][nxt] = ++cnt; root = trie[root][nxt]; } f[root] = 1; } void getfail() { queue<int>q; for (int i = 0; i < 4; ++i) if (trie[0][i]) q.push(trie[0][i]), fail[trie[0][i]] = 0; while (!q.empty()) { int now = q.front(); q.pop(); if (f[fail[now]]) f[now] = 1; for (int i = 0; i < 4; ++i) { if (trie[now][i]) { q.push(trie[now][i]); fail[trie[now][i]] = trie[fail[now]][i]; } else trie[now][i] = trie[fail[now]][i]; } } } Mat mul(const Mat& a, const Mat& b, int n) { Mat res; for (int i = 0; i <= n; ++i) for (int j = 0; j <= n; ++j) for (int k = 0; k <= n; ++k) (res.mat[i][j] += a.mat[i][k] * b.mat[k][j]) %= mod; return res; } Mat pow(Mat a, int n, ll p) { Mat res; for (int i = 0; i <= n; ++i) res.mat[i][i] = 1; while (p) { if (p & 1) res = mul(res, a, n); a = mul(a, a, n); p >>= 1; } return res; } int dp[N][N]; void solve(ll n) { Mat ans; for (int i = 0; i <= cnt; ++i) { if (f[i]) continue; for (int j = 0; j < 4; ++j) { if (f[trie[i][j]]) continue; ans.mat[i][trie[i][j]] += 1; } } ans = pow(ans, cnt, n); ll tot = 0; for (int i = 0; i <= cnt; ++i) (tot += ans.mat[0][i]) %= mod; printf("%lld\n", tot); } int main() { mp['A'] = 0; mp['C'] = 1; mp['G'] = 2; mp['T'] = 3; int n; ll m; scanf("%d%lld", &n, &m); for (int i = 1; i <= n; ++i) { scanf("%s", s); insert(s, strlen(s)); } getfail(); solve(m); return 0; }
$*$吐槽一下,都0202年了POJ还不支持C++11和万能头