1. 程式人生 > 實用技巧 >「JSOI2007」文字生成器

「JSOI2007」文字生成器

知識點: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|\),答案即為:

\[26^m - \sum_{i=0}^{|T|} f_{m,i} \pmod{10^4+7} \]

總時間複雜度 \(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;
}