【NOIP2013模拟联考8】匹配(match) 题解
B 组都说看不懂……我也解释不清啊……只能写这么详细了
其实就是道板题
省流:f[i][s][j]表示字符串长度i,匹配情况s,ac自动机节点j
Problem Description
给定k个字符串以及长度为n的母串可选字母的集合,问母串要完整出现给定的k个字符串的方案数,答案模1000000007,字符仅包含小写字母。
Input
第一行两个整数n、k,表示字符串的长度和给定字符串的个数。
接下来k行每行一个字符串。
接下来一行1个整数m表示可选字母集合内元素个数。
接下来一行给出一个长为m的字符串,表示字母的集合(可能有重复)。
Output
一个整数ans,表示方案数。
Sample Input Copy
3 2 cr rh 4 acrh Sample Output Copy
1 【样例解释】 只有crh符合。 Data Constraint
30%的数据n<=10,m<=3。
60%的数据n<=40。
另有10%的数据k=0。
另有10%的数据m=1。
100%的数据n<=100,m<=10,k<=8,给定字符串长度<=30。
先想一个弱化版问题:给定 \(k\) 个模式串以及长度为 \(n\) 的母串可选字母的集合,对于一个母串,它的价值为出现模式串的个数。问所有合法母串的价值和。
怎么才能判定一个母串是否包含几个模式串?
我们可以想到 ac 自动机,考虑对模式串建 ac 自动机,定义 tail
为以一个节点为模式串结尾的个数。如果我们跑到了一个标记为 tail
的节点,说明我们的母串包含了这一个模式串。
模仿 ac 自动机的过程,用 \(f[i][j]\) 表示母串长度为 \(i\),走到了自动机上的节点 \(j\) 的价值和,然后枚举它的下一个字符 \(c\),明显有 \(f[i+1][son[j][c]]\gets f[i][j] + tail[son[j][c]]\)。
回到原问题,因为我们要求完整出现给定的 \(k\) 个模式串的方案数,所以我们状压模式串的出现情况为 \(s\)。
然后定义 tail
为以一个节点为模式串结尾的状压出现情况。
同时,我们在 ac 自动机中有一个跳 fail
的步骤,这个步骤不是线性的,因为我们此时已经状压了,就可以直接在建树时把它传给子节点,有 \(tail[u]|=tail[fail[u]]\)。
所以我们设 \(f[i][s][j]\) 表示我们母串的长度为 \(i\),模式串的匹配状态为 \(s\)(状压后),当前母串跑到了 ac 自动机的节点 \(j\) 的方案数。
明显有 \(f[i+1][s|tail[son[j][c]]][son[j][c]]\gets f[i][s][j]\)。
#include <cstdio> #include <queue> using namespace std; #define N 110 #define M 32 #define K 8 #define P 1000000007 int n, m, k, cnt, ans; char s[M]; int can[M]; int f[2][1<<K][N*M]; int nxt[N*M][26]; int tail[N*M]; int fail[N*M]; bool vis[26]; void insert(int x) { int p = 0; for(int i = 1; s[i] != '\0'; i++) { if(!nxt[p][s[i]-'a']) { nxt[p][s[i]-'a'] = ++cnt; } p = nxt[p][s[i]-'a']; } tail[p] |= (1 << x); } void build() { queue<int> q; for(int i = 1; i <= m; i++) if(nxt[0][can[i]]) q.push(nxt[0][can[i]]); while(!q.empty()) { int p = q.front(); q.pop(); tail[p] |= tail[fail[p]]; for(int i = 1; i <= m; i++) { if(nxt[p][can[i]]) { fail[nxt[p][can[i]]] = nxt[fail[p]][can[i]]; q.push(nxt[p][can[i]]); } else { nxt[p][can[i]] = nxt[fail[p]][can[i]]; } } } } int main() { scanf("%d %d", &n, &k); for(int i = 0; i < k; i++) { scanf("%s", s+1); insert(i); } scanf("%d", &m); m = 0; scanf("%s", s+1); for(int i = 1; s[i] != '\0'; i++) { if(!vis[s[i]-'a']) { vis[s[i]-'a'] = 1; can[++m] = s[i]-'a'; } } build(); f[0][0][0] = 1; for(int i = 0; i < n; i++) { for(int s = 0; s < (1<<k); s++) { for(int p = 0; p <= cnt; p++) { f[(i+1) % 2][s][p] = 0; } } for(int s = 0; s < (1<<k); s++) { for(int p = 0; p <= cnt; p++) { if(!f[i%2][s][p]) continue; for(int j = 1; j <= m; j++) { int pp = nxt[p][can[j]]; (f[(i+1) % 2][s | tail[pp]][pp] += f[i%2][s][p]) %= P; } } } } for(int i = 0; i <= cnt; i++) { (ans += f[n%2][(1<<k)-1][i]) %= P; } printf("%d", ans); }
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现