「JSOI2007」文字生成器
阿新 • • 發佈:2021-01-09
知識點:ACAM,DP
原題面:Luogu
簡述
給定 \(n\) 個只由大寫字母構成的模式串 \(s_1\sim s_n\),給定引數 \(m\)。
求有多少個長度為 \(m\) 的只由大寫字母構成的字串,滿足其中至少有一個給定的模式串,答案對 \(10^4 + 7\) 取模。
\(1\le n\le 60\),\(1\le |s_i|,m\le 100\)。
1S,128MB。
分析
?這做法是個套路
先建立 ACAM,建 Trie 圖的時候順便標記所有包含模式串的狀態。記這些狀態構成集合 \(\mathbf{S}\)。
發現不好處理含有多個模式串的情況,考慮補集轉化,答案為所有串的個數 \(26^{m}\)
考慮 ACAM 上 DP。設 \(f_{i,j}\) 表示長度為 \(i\),在 ACAM 上匹配的結束狀態為 \(j\),不含模式串的字串的個數。
初始化空串 \(f_{0,0} = 1\)。轉移時列舉串長,狀態,轉移函式,避免轉移到包含模式串的狀態,有: \[f_{i,j} = \begin{cases} &\sum\limits_{\operatorname{trans}(u, k) = j} f_{i-1, u} &(j\notin \mathbf{S})\\ &0 &(j\in \mathbf{S}) \end{cases}\]
注意轉移時需要列舉空串的狀態 0。實現時滾動陣列 + 填表即可。
記 Trie 的大小為 \(|T|\),答案即為:
總時間複雜度 \(O(m|T||\sum|)\) 級別。
為什麼可以這樣轉移?
可以發現建立 Trie 圖後,這個轉移過程就相當於字串的匹配過程。
可以認為 DP 過程是通過所有長度為 \(i-1\) 的字串在 ACAM 上做匹配,從而得到長度為 \(i\) 的字串對應的狀態。
程式碼
//知識點:ACAM /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #include <queue> #define LL long long const int kN = 100 + 10; const int mod = 1e4 + 7; //============================================================= int n, m, ans; char s[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) { w = (w << 3) + (w << 1) + (ch ^ '0'); } return f * w; } void Chkmax(int &fir_, int sec_) { if (sec_ > fir_) fir_ = sec_; } void Chkmin(int &fir_, int sec_) { if (sec_ < fir_) fir_ = sec_; } namespace ACAM { int node_num, tr[60 * kN][26], fail[60 * kN], f[2][60 * kN]; bool tag[60 * kN]; void Insert(char *s_) { int u_ = 0, lth = strlen(s_ + 1); for (int i = 1; i <= lth; ++ i) { if (! tr[u_][s_[i] - 'A']) tr[u_][s_[i] - 'A'] = ++ node_num; u_ = tr[u_][s_[i] - 'A']; } tag[u_] = true; } void Build() { std::queue <int> q; for (int i = 0; i < 26; ++ i) { if (tr[0][i]) q.push(tr[0][i]); } while (! q.empty()) { int u_ = q.front(); q.pop(); for (int i = 0; i < 26; ++ i) { int v_ = tr[u_][i]; if (v_) { fail[v_] = tr[fail[u_]][i]; tag[v_] |= tag[fail[v_]]; q.push(v_); } else { tr[u_][i] = tr[fail[u_]][i]; } } } } void Query() { ans = f[0][0] = 1; for (int i = 1; i <= m; ++ i) ans = 26ll * ans % mod; for (int i = 1, now = 1; i <= m; ++ i, now ^= 1) { memset(f[now], 0, sizeof (f[now])); //caution:reset for (int j = 0; j <= node_num; ++ j) { for (int k = 0; k < 26; ++ k) { if (tag[tr[j][k]]) continue; f[now][tr[j][k]] += f[now ^ 1][j]; f[now][tr[j][k]] %= mod; } } } for (int i = 0; i <= node_num; ++ i) { ans = (ans - f[m % 2][i] + mod) % mod; } } } //============================================================= int main() { n = read(), m = read(); for (int i = 1; i <= n; ++ i) { scanf("%s", s + 1); ACAM::Insert(s); } ACAM::Build(); ACAM::Query(); printf("%d\n", ans); return 0; }