【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);
}