AC自动机相关:

$fail$树:

$fail$树上以最长$border$关系形成父子关系,我们定一个节点对应的串为根到该节点的路径。

对于任意一个非根节点$x$,定$y = fa_{x}$,那$y$对应的串就是$x$对应的串的最长$border$,也就是说如果母串能走到$x$,那母串中一定存在一个子串对应了$y$,而且是当前母串匹配到当前位置的一个后缀。

求每个模式串在母串中出现的次数:

这应该算是AC自动机最基本的问题。

把母串在自动机上跑一遍,显然所有被访问过的节点都是母串的子串,但以当前匹配位置为后缀的模式串可能不仅仅只有一个,还有它所有的$border$。一个很好的性质就是,$fail$树上某个节点的祖先链就恰好完全对应了该节点的所有$border$。我们用$cnt_{i}$表示$i$点的祖先链上有效节点的个数,那只要每次匹配到某个节点$x$时,把它的$cnt_{x}$加上贡献就行了。

如果要支持动态加减模式串,由于修改一个节点的有效性只会对它的子树中所有节点造成影响,只要维护$Dfs$序上的信息即可。

 

AC自动机上的$Dp$问题

通常情况下,这类$Dp$问题的数据范围不大,设计状态的基本套路通常有两维,一维是母串匹配到的位置,一维是在自动机上的位置,转移基本上都是枚举字符。

$\star$ 一个简单的例子,就是给出一个数$n$和$m$个字符串,问长度为$n$的字符串有多少个满足这$m$个串都是它的子串。题目链接

由于$m$非常小,我们直接状压起来,表示该节点对应的串包含了哪些子串,$Dp$有两种方式,$Dfs$和$Bfs$都可以,如果$Dfs$的话状态的含义通常表示为当前状态到结束状态的方案数。

不过由于这题要输出方案,用$Bfs$的做法不太容易,还是用$Dfs$的吧。

#include <cstdio>
#include <queue>
#include <cstring>

using namespace std;

typedef long long LL;
const int N = 27, M = 135;

int n, m, ST, tot;
char sr[29];
int ch[M][27], fail[M], fir[M];
queue<int> Q;

LL dp[N][M][1037], ans;

void Ins(char *s, int id) {
  int pos = 0;
  for (int i = 0; s[i]; ++i) {
    int nx = s[i] - 'a';
    if (!ch[pos][nx]) {
      ch[pos][nx] = ++tot;
      memset(ch[tot], 0, sizeof ch[tot]);
      fir[tot] = fail[tot] = 0;
    }
    pos = ch[pos][nx];
  }
  fir[pos] |= 1 << id;
}

void Build() {
  Q.push(0);
  for (; !Q.empty(); ) {
    int x = Q.front(); Q.pop();
    for (int i = 0; i < 26; ++i) {
      int v = ch[x][i];
      if (!v) ch[x][i] = ch[fail[x]][i]; else Q.push(v);
      if (x && v) fail[v] = ch[fail[x]][i], fir[v] |= fir[fail[v]];
    }
  }
}

LL Dfs(int x, int p, int s) {
  if (~dp[x][p][s]) return dp[x][p][s];
  if (x == n) {
    dp[x][p][s] = (LL) (s == ST);
    return dp[x][p][s];
  }
  dp[x][p][s] = 0;
  for (int i = 0; i < 26; ++i) {
    int np = ch[p][i];
    dp[x][p][s] += Dfs(x + 1, np, s | fir[np]);
  }
  return dp[x][p][s];
}

void F_(int x, int p, int s) {
  if (x == n) {
    sr[x] = '\0';
    printf("%s\n", sr);
    return;
  }
  for (int i = 0; i < 26; ++i) {
    int np = ch[p][i];
    if (dp[x + 1][np][s | fir[np]]) {
      sr[x] = 'a' + i;
      F_(x + 1, np, s  | fir[np]);
      sr[x] = '\0';
    }
  }
}

void Clear() {
  memset(dp, -1, sizeof dp);
  memset(ch[0], 0, sizeof ch[0]);
  tot = ans = 0;
}

int main() {
  for (int tc = 0; scanf("%d%d", &n, &m) == 2 && n + m > 0; ) {
    Clear();
    ST = (1 << m) - 1;
    for (int i = 0; i < m; ++i) {
      scanf("%s", sr);
      Ins(sr, i);
    }
    Build();
    ans = Dfs(0, 0, 0);
    printf("Case %d: %lld suspects\n", ++tc, ans);
    if (ans <= 42) {
      memset(sr, 0, sizeof sr);
      F_(0, 0, 0);
    }
  }
  
  return 0;
}
View Code

相关文章:

  • 2022-03-05
  • 2022-01-17
  • 2021-12-03
  • 2022-03-08
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2021-06-10
  • 2021-04-20
  • 2022-12-23
  • 2022-02-14
  • 2021-05-28
相关资源
相似解决方案