洛谷P4052 [JSOI2007]文本生成器 AC自动机上dp
网址:https://www.luogu.com.cn/problem/P4052
题意:
给出$n$个长度为$m$的字符串,求长度为$k$的仅包含大写字母的字符串中至少包含一个给定的字符串的字符串的数量,结果对$10007$取模$(n \leq 60,m \leq 100,k \leq 1200)$。
题解:
正向考虑似乎比较困难,要找恰好有$1,2,......n$个模式串的字符串。所以我们反向考虑,求所有方案减去一个模式串都没有的方案。这里我们考虑$AC$自动机。
我们在POJ2778讨论了常见的$AC$自动机上$dp$的两种方法。我们考虑第二种,其时间复杂度是$O(n^3m^3logk)$显然无法通过。现在考虑第一种,其时间复杂度是$O(nmk)$,可以通过,然后我们就按照$dp$方程求解即可,$ans=26^n- \sum _{0<i \leq n*m} dp(k,i)$。
AC代码:
#include <bits/stdc++.h> using namespace std; const int mod = 10007; const int N = 6e3 + 5; const int M = 26; int trie[N][M], fail[N]; int dp[N][N]; bool f[N]; int cnt; void insert(char* s, int len) { int root = 0; for (int i = 0; i < len; ++i) { int nxt = s[i] - 'A'; if (!trie[root][nxt]) trie[root][nxt] = ++cnt; root = trie[root][nxt]; } f[root] = 1; } void get_fail() { queue<int>q; for (int i = 0; i < M; ++i) if (trie[0][i]) q.push(trie[0][i]); while (!q.empty()) { int now = q.front(); q.pop(); for (int i = 0; i < M; ++i) { if (trie[now][i]) { fail[trie[now][i]] = trie[fail[now]][i]; f[trie[now][i]] |= f[fail[trie[now][i]]]; q.push(trie[now][i]); } else trie[now][i] = trie[fail[now]][i]; } } } int pow(int a, int b, int p) { int res = 1; while (b) { if (b & 1) res = res * a % p; a = a * a % p; b >>= 1; } return res; } void solve(int n) { dp[0][0] = 1; for (int i = 1; i <= n; ++i) for (int j = 0; j <= cnt; ++j) if (!f[j]) for (int k = 0; k < M; ++k) { dp[i][trie[j][k]] += dp[i - 1][j]; dp[i][trie[j][k]] %= mod; } int ans = 0; for (int i = 0; i <= cnt; ++i) if (!f[i]) (ans += dp[n][i]) %= mod; printf("%d", (pow(26, n, mod) - ans % mod + mod) % mod); } char s[105]; int main() { int n, m; scanf("%d%d", &n, &m); for (int i = 1; i <= n; ++i) { scanf("%s", s); insert(s, strlen(s)); } get_fail(); solve(m); return 0; }