【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);
}
posted @ 2024-03-13 22:18  ZnPdCo  阅读(14)  评论(0编辑  收藏  举报