[BZOJ 1559] [JSOI2009] 密码 【AC自动机DP】
题目链接:BZOJ - 1559
题目分析
将给定的串建成AC自动机,然后在AC自动机上状压DP。
转移边就是Father -> Son 或 Now -> Fail。
f[i][j][k] ,表示到了字符串第 i 位,在AC自动机的第 j 个节点上,状态为 k 的方案数。
状态 k 是一个二进制压缩的,表示已经包含了哪些给定串的整数。
然后...输出方案....这个太麻烦了...我是从最后状态DFS向前反推。
另外的问题是我写的AC自动机DP无法正确处理给定串存在串 A 包含串 B 的情况,所以我必须将被其他给定串包含的给定串忽略。
要去掉所有被其他某个给定串包含的给定串。需要在建完Fail之后从每个节点向Fail一直走到Root,将一路上的所有节点都设定为不是给定串。
当然要记录一下某个节点沿Fail到Root的路径已经被处理的话就 break。
我刚开始写的代码只能处理一个串是另一个串前缀或后缀的情况,被包含在中间的情况不能处理,但是仍然在BZOJ AC了。
代码
#include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> #include <queue> using namespace std; const int MaxL = 15, MaxM = 15, MaxC = 27, MaxN = 25 + 5, MaxNode = 255 + 5; typedef long long LL; int n, m, l, Index, Tot, MT, Top; LL Ans; LL f[MaxN][MaxNode][1024 + 15]; char S[MaxL]; struct Str { char str[MaxN]; } Sol[50], DS; bool Cmp(Str s1, Str s2) { for (int i = 0; i < n; ++i) { if (s1.str[i] == s2.str[i]) continue; return s1.str[i] < s2.str[i]; } return false; } struct Trie { int Num, ID, c, isStr; bool Ed; Trie *Child[MaxC], *Fail; } TA[MaxNode], *P = TA, *Root, *Zero; void Insert(char *S, int l) { Trie *Now = Root; int t; for (int i = 1; i <= l; ++i) { t = S[i] - 'a'; if (Now -> Child[t] == NULL) { ++P; P -> Num = 0; P -> ID = ++Tot; P -> c = t; Now -> Child[t] = P; } Now = Now -> Child[t]; } Now -> isStr = 1; } void Init_AC() { Index = 0; Tot = 0; Zero = P; //ID : 0 Root = ++P; Root -> ID = ++Tot; //ID : 1 for (int i = 0; i < 26; ++i) Zero -> Child[i] = Root; Zero -> Fail = NULL; for (int i = 0; i < 26; ++i) Root -> Child[i] = NULL; Root -> Fail = Zero; } queue<Trie *> Q; void Build_Fail() { while (!Q.empty()) Q.pop(); Q.push(Root); Trie *Now, *Temp; while (!Q.empty()) { Now = Q.front(); Q.pop(); for (int i = 0; i < 26; ++i) { if (Now -> Child[i] == NULL) Now -> Child[i] = Now -> Fail -> Child[i]; else { Now -> Child[i] -> Fail = Now -> Fail -> Child[i]; Q.push(Now -> Child[i]); } } } for (Trie *j = TA; ; ++j) { Temp = j -> Fail; while (Temp != NULL && Temp != Root && Temp != Zero) { if (Temp -> Ed) break; Temp -> Ed = true; Temp -> isStr = -1; Temp = Temp -> Fail; } if (j == P) break; } for (Trie *j = TA; ; ++j) { if (j -> isStr == 1) j -> Num = ++Index; else j -> Num = 0; if (j == P) break; } } void DP() { f[0][1][0] = 1; MT = (1 << Index) - 1; for (int i = 0; i <= n; ++i) for (int j = 0; j <= Tot; ++j) for (int k = 0; k <= MT; ++k) if (f[i][j][k]) { for (int t = 0; t < 26; ++t) { if (TA[j].Child[t] -> Num == 0) f[i + 1][TA[j].Child[t] -> ID][k] += f[i][j][k]; else f[i + 1][TA[j].Child[t] -> ID][k | (1 << (TA[j].Child[t] -> Num - 1))] += f[i][j][k]; } } Ans = 0; for (int i = 1; i <= Tot; ++i) Ans += f[n][i][MT]; } void DFS(int l, int x, int y, int t) { //printf("Begin %d %d %d %c\n", l, x, y, t + 'a'); DS.str[l - 1] = 'a' + t; if (l == 1) { Sol[++Top] = DS; return; } for (int i = 1; i <= Tot; ++i) if (f[l - 1][i][y] && TA[i].Child[t] -> ID == x) { if (i == 1) for (int j = 0; j < 26; ++j) DFS(l - 1, i, y, j); else DFS(l - 1, i, y, TA[i].c); } int yy; if (TA[x].Num != 0) { yy = y - (1 << (TA[x].Num - 1)); for (int i = 1; i <= Tot; ++i) if (f[l - 1][i][yy] && TA[i].Child[t] -> ID == x) { if (i == 1) for (int j = 0; j < 26; ++j) DFS(l - 1, i, yy, j); else DFS(l - 1, i, yy, TA[i].c); } } //printf("End %d %d %d %c\n", l, x, y, t + 'a'); } void Get_Sol() { Top = 0; for (int i = 1; i <= Tot; ++i) if (f[n][i][MT]) { if (i == 1) { for (int j = 0; j < 26; ++j) DFS(n, i, MT, j); } else DFS(n, i, MT, TA[i].c); } } int main() { scanf("%d%d", &n, &m); Init_AC(); Index = 0; for (int i = 1; i <= m; ++i) { scanf("%s", S + 1); l = strlen(S + 1); Insert(S, l); } Build_Fail(); DP(); printf("%lld\n", Ans); if (Ans <= 42) { Get_Sol(); sort(Sol + 1, Sol + Ans + 1, Cmp); for (int i = 1; i <= Ans; ++i) { Sol[i].str[n] = 0; printf("%s\n", Sol[i].str); } } return 0; }