[BZOJ 1559] [JSOI2009] 密码 【AC自动机DP】

时间:2023-03-08 15:40:32
[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;
}