P4052题解

我们来看一下这道题:

首先建出 $trie$ 树,再建出 AC自动机

考虑容斥,即总数 - 不可读的个数。

总数 = $26 ^ m$,可用快速幂求解。

问题变成 不可读的方案 有多少个?

显然,如果一个点 $u$,$fail[u]$ 是危险节点,则 $u$ 也是危险节点。

那么用一个 $flag[u]$ 表示 $u$ 是否是危险节点,$flag[u]$ 可在求 $fail$ 时转移。

考虑 动态规划,记 $dp[i][j]$ 表示在AC自动机上结尾为节点 $j$,长度为 $i$ 的方案数,转移方程为:

$$dp[i][trie[j][k]] = dp[i][trie[j][k]] + dp[i - 1][j],flag[trie[j][k]] \neq 0$$

最后方案就是:$\sum\limits_{i = 1}^{cnt} dp[m][i]$

注意:要取摸,且处理取摸后再减为负数的情况

具体实现,可看代码。

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

const int maxn = 6e4 + 10, mod = 1e4 + 7;
int n, m, cnt = 1, trie[maxn][26], fail[maxn], dp[110][maxn];
char c[maxn][110];
bool flag[maxn];
queue <int> q;

void insert (int p) { // 建立 trie 树
    int now = 1, len = strlen (c[p] + 1);
    for (int i = 1; i <= len; ++i) {
        int x = c[p][i] - 'A';
        if (!trie[now][x]) trie[now][x] = ++ cnt;
        now = trie[now][x];
    }
    flag[now] = 1;
}

void calc () { // 求出 fail 与 flag 数组,便建出 AC 自动机
    for (int i = 0; i < 26; ++i) trie[0][i] = 1;
    q.push (1); fail[1] = 0;
    while (!q.empty()) {
        int now = q.front(); q.pop();
        for (int i = 0; i < 26; ++i) {
            if (trie[now][i]) {
                fail[trie[now][i]] = trie[fail[now]][i]; flag[trie[now][i]] |= flag[fail[trie[now][i]]];
                q.push (trie[now][i]);
            } else {
                trie[now][i] = trie[fail[now]][i];
            }
        }
    }
}

int query () { // 求不可行方案
    dp[0][1] = 1;
    for (int i = 1; i <= m; ++i) {
        for (int j = 1; j <= cnt; ++j) {
            for (int k = 0; k < 26; ++k) {
                if (!flag[trie[j][k]]) dp[i][trie[j][k]] = (dp[i][trie[j][k]] + dp[i - 1][j]) % mod;
            }
        }
    }
    int ans = 0;
    for (int i = 1; i <= cnt; ++i) {ans = (ans + dp[m][i]) % mod; cout << ans << endl;}
    return ans;
}

int pow (int x, int y) { // 快速幂
    int res = 1;
    while (y) {
        if (y & 1) res = res * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return res;
}

int main() {
    scanf ("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) {
        scanf ("%s", c[i] + 1); insert (i); // 插入
    }
    calc ();
    printf ("%d", (pow (26, m) - query() + mod) % mod); // 容斥
    return 0;
}
posted @ 2021-07-30 11:39  wangzhongyuan  阅读(18)  评论(0编辑  收藏  举报  来源