AC自动机总结

本文详细介绍了AC自动机(Aho-Corasick自动机)在字符串处理问题中的应用,包括文本生成器、视频游戏、数数、玄武密码、DNA序列和单词情结等题目。通过AC自动机结合动态规划、矩阵快速幂等方法,解决了多个涉及字符串匹配和计数的问题。文章还提供了多个AC自动机的AC代码实例,展示了如何利用AC自动机解决实际问题。
摘要由CSDN通过智能技术生成

前言

写了一下AC自动机的题目,发现蛮喜欢出AC自动机+dp的题目。而且dp状态通常为 d p [ i ] [ j ] dp[i][j] dp[i][j]表示已经串长 i i i,然后匹配到了自动机的 j j j节点。这里写一下总结

P4052 [JSOI2007]文本生成器

题目链接:[JSOI2007]文本生成器
题目大意:给定 n n n个单词,求出有多少个长 m m m的文章包含至少一个单词。
数据范围: 1 ≤ n ≤ 60 , 1 ≤ m ≤ 100 , 1 ≤ ∣ s i ∣ ≤ 100 。 1≤n≤60,1 \leq m \le100 ,1≤|s_i|≤100。 1n60,1m100,1si100
题解:AC自动机+dp。这道题在去年还是个蒟蒻的时候就已经放入我的收藏里面了,说的好像现在不是蒟蒻一样 。不过一直没去写(主要是太菜了)。我们来分析一下,首先很容易的想到容斥一下 a n s = 至 少 一 个 s i → 2 6 m − ( 一 个 都 不 包 含 ) ans=至少一个s_i\to 26^m-(一个都不包含) ans=si26m()现在问题变成如何求出不包含 s i s_i si的串数量。考虑 d p dp dp, d p [ i ] [ j ] dp[i][j] dp[i][j]表示已经串长 i i i,然后匹配到了自动机的 j j j节点的串个数。想进行 d p dp dp的转移还需要解决一个问题,就是有些节点是不能到达的,因为符号要求的串是不能包含 s i s_i si的,所以我们需要预处理出来,这个我们在 b u i l d build build函数中解决。剩下的就是递推了。具体看代码吧。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 10000 + 10;
const int mod = 1e4 + 7;
int nxt[N][26],tot,pd[N],fail[N],dp[105][N];
int n,m;
void insert(char* s)
{
	int n = strlen(s);
	int now = 0;
	for (int i = 0; i < n; i++)
	{
		if (!nxt[now][s[i] - 'A'])
			nxt[now][s[i] - 'A'] = ++tot;
		now=nxt[now][s[i] - 'A'];
	}
	pd[now] = 1;
}
void build()
{
	queue<int>pls;
	for (int i = 0; i < 26; i++)if (nxt[0][i])pls.push(nxt[0][i]), fail[nxt[0][i]] = 0;
	while (pls.size())
	{
		int f = pls.front(); pls.pop();
		for (int i = 0; i < 26; i++)
		{
			if (nxt[f][i])
			{
				fail[nxt[f][i]] = nxt[fail[f]][i];
				pd[nxt[f][i]] |= pd[nxt[fail[f]][i]];
				pls.push(nxt[f][i]);
			}
			else
				nxt[f][i] = nxt[fail[f]][i];
		}
	}
}
char s[N];
int fpow(int x, int y)
{
	int ans = 1;
	while (y)
	{
		if (y & 1)ans = ans * x % mod;
		x = x * x % mod; y >>= 1;
	}
	return ans;
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	read(n), read(m);
	for (int i = 1; i <= n; i++)
	{
		scanf("%s", s); insert(s);
	}
	build();
	dp[0][0] = 1;//dp[i][j]表示已经长i,匹配到了j节点
	for (int i = 1; i <= m; i++)
	{
		for (int j = 0; j <= tot; j++)
		{
			for (int nx = 0; nx < 26; nx++)
				if (!pd[nxt[j][nx]])
					dp[i][nxt[j][nx]] = (dp[i][nxt[j][nx]] + dp[i - 1][j]) % mod;
		}
	}
	int ans = fpow(26, m);
	for (int j = 0; j <= tot; j++)ans = (ans - dp[m][j]) % mod;
	printf("%lld\n", (ans % mod + mod) % mod);
	return 0;
}

P3041 [USACO12JAN]Video Game G

题目链接:[USACO12JAN]Video Game G
题目大意:给定 n n n个单词,求出有多少个长 k k k的文章最大匹配单词多少次。
数据范围: 1 ≤ n ≤ 20 , 1 ≤ k ≤ 1 0 3 , 1 ≤ ∣ s i ∣ ≤ 15 1≤n≤20,1 \leq k \leq 10^3,1≤∣s_i∣≤15 1n20,1k103,1si15
题解:AC自动机+dp,和上面一题是一样的,只不过不用处理那些点不能转移到。然后 d p dp dp变成求 m a x max max。其实 d p dp dp就两个问题, d p dp dp数组的初始化以及状态方程的转移。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 1000 + 10;
const int mod = 1e9 + 7;
int n, k;
int nxt[N][3], fail[N], val[N],tot;
void insert(char* s)
{
	int now = 0;
	int n = strlen(s);
	for (int i = 0; i < n; i++)
	{
		if (!nxt[now][s[i] - 'A'])nxt[now][s[i] - 'A'] = ++tot;
		now = nxt[now][s[i] - 'A'];
	}
	val[now]++;
}
void build()
{
	queue<int>pls;
	for (int i = 0; i < 3; i++)if (nxt[0][i])pls.push(nxt[0][i]);
	while (pls.size())
	{
		int f = pls.front(); pls.pop();
		for (int i = 0; i < 3; i++)
		{
			if (nxt[f][i])
			{
				fail[nxt[f][i]] = nxt[fail[f]][i];
				val[nxt[f][i]] += val[nxt[fail[f]][i]];
				pls.push(nxt[f][i]);
			}
			else
				nxt[f][i] = nxt[fail[f]][i];
		}
	}
}
char s[N];
int dp[1005][N];
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	read(n), read(k);
	for (int i = 1; i <= n; i++)
	{
		scanf("%s", s); insert(s);
	}
	build();
	memset(dp, -0x3f, sizeof(dp));
	dp[0][0] = 0;
	for (int i = 1; i <= k; i++)
	{
		for (int j = 0; j <= tot; j++)
		{
			for (int nx = 0; nx < 3; nx++)
			{
				dp[i][nxt[j][nx]] =max(dp[i][nxt[j][nx]], dp[i - 1][j] + val[nxt[j][nx]]);
			}
		}
	}
	int ans = 0;
	for (int j = 0; j <= tot; j++)ans = max(ans, dp[k][j]);
	printf("%lld\n", ans);
	return 0;
}

P3311 [SDOI2014] 数数

题目链接:[SDOI2014] 数数
题目大意:给定 m m m个数字串,求出有不超过 n n n的有多少个数不包含给定数字串。
数据范围: 1 ≤ n < 1 0 1201 , 1 ≤ m ≤ 100 , 1 ≤ ∑ i = 1 m ∣ s i ∣ ≤ 1500 1≤n<10^{1201},1 \leq m \leq 100,1 \leq \sum_{i = 1}^m |s_i| \leq 1500 1n<101201,1m100,1i=1msi1500
题解:AC自动机+数位dp。非常好的一道题,细节蛮多的。主要解释一下 d f s dfs dfs函数, c u r cur cur表示当前还剩多少位, l i m i t limit limit表示是否顶着数位的上界, p o s pos pos表示当前在自动机的什么位置,_0表示是否有前导零。具体细节看代码。
AC代码:

#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int nxt[N][10], fail[N],tot, pd[N];
void insert(char* s)
{
	int now = 0, n = strlen(s);
	for (int i = 0; i < n; i++)
	{
		if (!nxt[now][s[i] - '0'])
			nxt[now][s[i] - '0'] = ++tot;
		now = nxt[now][s[i] - '0'];
	}
	pd[now] = 1;
}
void build()
{
	queue<int>pls;
	for (int i = 0; i < 10; i++)if (nxt[0][i])pls.push(nxt[0][i]);
	while (pls.size())
	{
		int f = pls.front(); pls.pop();
		for (int i = 0; i < 10; i++)
		{
			if (nxt[f][i])
			{
				fail[nxt[f][i]] = nxt[fail[f]][i];
				pd[nxt[f][i]] |= pd[nxt[fail[f]][i]];//打上标记表示不能到达
				pls.push(nxt[f][i]);
			}
			else
				nxt[f][i] = nxt[fail[f]][i];
		}
	}
}
int dp[1205][1505],w[1205],m;
int dfs(int cur, int limit, int pos,int _0)
{
	if (!cur)return 1;
	if (!limit&&!_0 && ~dp[cur][pos])return dp[cur][pos];
	int tp = limit ? w[cur]:9;
	int ans = 0;
	for (int i = 0; i <= tp; i++)
	{
		if (!pd[nxt[pos][i]]||(i==0&&_0))//可以去
		{
			ans = (ans + dfs(cur - 1, limit && i == tp, (i == 0 && _0) ? 0 : nxt[pos][i], (i == 0 && _0))) % mod;
		}
	}

	if (!limit && !_0)dp[cur][pos] = ans;
	return (ans % mod + mod)%mod;
}
char s[N];
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	memset(dp, -1, sizeof(dp));
	scanf("%s", s + 1);
	w[0] = strlen(s + 1);
	reverse(s + 1, s + 1 + w[0]);
	for (int i = 1; i <= w[0]; i++)w[i] = s[i] - '0';
	read(m);
	
	for (int i = 1; i <= m; i++)
	{
		scanf("%s", s + 1);
		insert(s + 1);
	}
	build();

	printf("%lld\n",dfs(w[0], 1, 0, 1)-1);
	return 0;
}

P5231 [JSOI2012]玄武密码

题目链接: [JSOI2012]玄武密码
题目大意:给定一个母串 S S S,给出 m m m段文字,求每一段文字中最长的一段前缀满足是 S S S中的子串。
数据范围: 1 ≤ n ≤ 1 e 7 , 1 ≤ m ≤ 1 e 5 , 1 ≤ ∣ s i ∣ ≤ 100 1\le n\le 1e7,1\le m\le 1e5,1\le |s_i|\le 100 1n1e7,1m1e5,1si100
题解:AC自动机,我们将m段文字建成AC自动机,然后将母串放上去进行匹配,查询的时候只要将看文字可以到达的最远的已匹配点。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 10000000 + 10;
const int mod = 1e9 + 7;

int num(char c)
{
	if (c == 'E')return 0;
	if (c == 'S')return 1;
	if (c == 'W')return 2;
	return 3;
}

int n, m;
char s[N];
char a[100005][105];
int nxt[N][4],vis[N],fail[N],tot;
void insert(char* s)
{
	int n = strlen(s); int now = 0;
	for (int i = 0; i < n; i++)
	{
		if (!nxt[now][num(s[i])])nxt[now][num(s[i])] = ++tot;
		now = nxt[now][num(s[i])];
	}
}
void build()
{
	queue<int>pls;
	for (int i = 0; i < 4; i++)if (nxt[0][i])pls.push(nxt[0][i]);
	while (pls.size())
	{
		int f = pls.front(); pls.pop();
		for (int i = 0; i < 4; i++)
		{
			if (nxt[f][i])
			{
				fail[nxt[f][i]] = nxt[fail[f]][i];
				pls.push(nxt[f][i]);
			}
			else
				nxt[f][i] = nxt[fail[f]][i];
		}
	}
}

vector<int>p[N];
void dfs(int u)
{
	for (auto to : p[u])dfs(to), vis[u] |= vis[to];
}
int query(char* s)
{
	int ans = 0; int now = 0, n = strlen(s);
	for (int i = 0; i < n; i++)
	{
		now = nxt[now][num(s[i])];
		if (vis[now])ans = max(ans, i + 1);
	}
	return ans;
}
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	read(n); read(m);
	scanf("%s", s + 1);
	for (int i = 1; i <= m; i++)
	{
		scanf("%s", a[i] + 1); insert(a[i] + 1);
	}
	build();
	int now = 0;
	for (int i = 1; i <= n; i++)
	{
		now = nxt[now][num(s[i])]; vis[now] = 1;
	}
	for (int i = 1; i <= tot; i++)p[fail[i]].push_back(i);
	dfs(0);
	for (int i = 1; i <= m; i++)printf("%d\n", query(a[i] + 1));
	return 0;
}

POJ-DNA Sequence

题目链接:DNA Sequence
题目大意:给定 m m m个模板串,求长 n n n的串中不含模板串有多少个。
数据范围: 0 ≤ m ≤ 10 , 1 ≤ n ≤ 2 e 9 , 1 ≤ ∣ s i ∣ ≤ 10 0\le m\le 10,1\le n\le2e9,1\le|s_i|\le10 0m10,1n2e9,1si10
题解:AC自动机+矩阵优化DP。很经典的题,用来锻炼一下矩阵和自动机。令 d p [ i ] [ j ] dp[i][j] dp[i][j]表示长 i i i在自动机节点 j j j上合法的串数量。然后我们能发现 j j j的范围不会超过 100 100 100,所以考虑用矩阵来优化递推。时间复杂度 O ( ( ∑ ∣ s i ∣ ) 3 ∗ l o g n ) O((\sum{|s_i|})^3*logn) O((si)3logn)
AC代码:


#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<time.h>
#include<stdio.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}

const ll N = 200000 + 10;
const int mod = 100000;
struct mat
{
	int n, m;
	int a[105][105];
	mat(int x, int y)
	{
		n = x, m = y;
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				a[i][j] = 0;
	}
	mat()
	{
		n = 0, m = 0;
	}
	mat operator*(const mat& b)const
	{
		mat c(n, b.m);
		for (int i = 1; i <= c.n; i++)
			for (int j = 1; j <= c.m; j++)
				for (int k = 1; k <= m; k++)
					c.a[i][j] = (c.a[i][j] + 1ll * a[i][k] * b.a[k][j] % mod) % mod;
		return c;
	}
	void init()
	{
		for (int i = 1; i <= n; i++)a[i][i] = 1;
	}
	mat operator^(int y)const
	{
		mat x = *this;
		mat ans(n, m); ans.init();
		while (y)
		{
			if (y & 1)ans = ans * x;
			x = x * x; y >>= 1;
		}
		return ans;

	}
}base,ans;
int get(char c)
{
	if (c == 'A')return 0;
	if (c == 'C')return 1;
	if (c == 'T')return 2;
	return 3;
}
int tot = 1;
struct ACAM
{
	int nxt[N][4], fail[N], vis[N];
	void insert(char* s)
	{
		int n = strlen(s);
		int now = 1;
		for (int i = 0; i < n; i++)
		{
			if (!nxt[now][get(s[i])])nxt[now][get(s[i])] = ++tot;
			now = nxt[now][get(s[i])];
		}
		vis[now] = 1;//不能达到
	}
	void build()
	{
		queue<int>pls;
		for (int i = 0; i < 4; i++)
		{
			if (nxt[1][i])pls.push(nxt[1][i]), fail[nxt[1][i]] = 1;
			else
				nxt[1][i] = 1;
		}
		while (pls.size())
		{
			int f = pls.front(); pls.pop();
			for (int i = 0; i < 4; i++)
			{
				if (nxt[f][i])
				{
					fail[nxt[f][i]] = nxt[fail[f]][i];
					vis[nxt[f][i]] |= vis[nxt[fail[f]][i]];
					pls.push(nxt[f][i]);
				}
				else
					nxt[f][i] = nxt[fail[f]][i];
			}
		}
	}
}AC;
int n, m;
char s[N];
mat init(int x)
{
	mat ans(x, x);
	for (int i = 1; i <= x; i++)
	{
		if (AC.vis[i])continue;
		for (int nx = 0; nx < 4; nx++)
		{
			int to = AC.nxt[i][nx];
			if (AC.vis[to])continue;
			ans.a[i][to]++;
		}
	}

	return ans;
}
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	read(m), read(n);
	for (int i = 1; i <= m; i++)
	{
		scanf("%s", s + 1); AC.insert(s + 1);
	}
	AC.build();
	base = init(tot);
	base = base ^ n;
	mat ans(tot, tot);
	ans.a[1][1] = 1;
	ans = ans * base;
	ll sum = 0;
	for (int i = 1; i <= tot; i++)sum = (sum + ans.a[1][i]) % mod;
	printf("%lld\n", sum % mod);
	return 0;
}

POJ太慢了。这份代码是究极卡过去的。
在这里插入图片描述

HDU - 2243 考研路茫茫――单词情结

题目链接:HDU - 2243
题意:给定n个单词,求不超过 L L L其中至少包含一个单词的串数量。
数据范围: ∑ ∣ s i ∣ ≤ 30 , L ≤ 2 31 \sum|s_i|\le30,L\le2^{31} si30,L231
题解:AC自动机+矩阵优化DP。令 d p [ i ] [ j ] dp[i][j] dp[i][j]表示长 i i i在自动机节点 j j j一个单词不含的串数量。 a n s = ∑ i = 1 L 2 6 i − ∑ i = 1 i = L ∑ j ≤ t o t d p [ i ] [ j ] ans=\sum_{i=1}^{L}26^i-\sum_{i=1}^{i=L}\sum_{j\le tot} dp[i][j] ans=i=1L26ii=1i=Ljtotdp[i][j]这个式子的两项都需要用矩阵运算来计算。对于需要统计过程所有的值的和的题目,我们将矩阵扩充一列,全放上 1 1 1
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
#define ull unsigned long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200 + 10;
const int mod = 1e9 + 7;
struct mat
{
	ull a[32][32];
	ull n, m;
	mat(int x, int y)
	{
		n = x, m = y;
		memset(a, 0, sizeof(a));
	}
	mat(){}
	void init()
	{
		for (int i = 1; i <= n; i++)a[i][i] = 1;
	}
	mat operator *(const mat& b)
	{
		mat c(n, b.m);
		for (int i = 1; i <= c.n; i++)
			for (int j = 1; j <= c.m; j++)
				for (int k = 1; k <= m; k++)
					c.a[i][j] += a[i][k] * b.a[k][j];
		return c;
	}
	mat operator^(int y)
	{
		mat ans(n, n); ans.init();
		mat x = *this;
		while (y)
		{
			if (y & 1)ans = ans * x;
			x = x * x; y >>= 1;
		}
		return ans;
	}
}base;
int nxt[50][26], tot = 1, fail[N],vis[N];
void insert(char* s)
{
	int n = strlen(s);
	int now = 1;
	for (int i = 0; i < n; i++)
	{
		if (!nxt[now][s[i] - 'a'])nxt[now][s[i] - 'a'] = ++tot;
		now = nxt[now][s[i] - 'a'];
	}
	vis[now] = 1;
}
void build()
{
	queue<int>pls;
	for (int i = 0; i < 26; i++)
		if (nxt[1][i])pls.push(nxt[1][i]), fail[nxt[1][i]] = 1;
		else nxt[1][i] = 1;
	while (pls.size())
	{
		int f = pls.front(); pls.pop();
		for (int i = 0; i < 26; i++)
		{
			if (nxt[f][i])
			{
				fail[nxt[f][i]]= nxt[fail[f]][i];;
				vis[nxt[f][i]] |= vis[fail[nxt[f][i]]];
				pls.push(nxt[f][i]);
			}
			else
				nxt[f][i] = nxt[fail[f]][i];
		}
	}
}
void init()
{
	memset(nxt, 0, sizeof(nxt));
	memset(vis, 0, sizeof(vis));
	memset(fail, 0, sizeof(fail));
	tot = 1;
}
mat getmat(int x)
{
	mat ans(x, x);
	for (int i = 1; i <= x-1; i++)
	{
		if (vis[i])continue;
		for (int j = 0; j < 26; j++)
		{
			int to = nxt[i][j];
			if (vis[to])continue;
			ans.a[i][to]++;
		}
	}
	for (int i = 1; i <= x; i++)ans.a[i][x]=1;
	return ans;
}
int n, L;
char s[N];
ull calcsum(ll x)
{
	mat t(2, 2);
	t.a[1][1] = 26, t.a[1][2] = t.a[2][2] = 1;
	t = t ^ (x);
	return t.a[1][1]+t.a[1][2];
}
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	while (scanf("%d%d", &n, &L) != EOF)
	{
		init();
		for (int i = 1; i <= n; i++)scanf("%s", s + 1), insert(s + 1);
		build();
		base = getmat(tot+1);
		mat ans(tot+1, tot+1);
		ans.a[1][1] = 1;
		base = (base ^ (L));
		ans = ans*base;
		ull nans = calcsum(L);
		//nans -= ans.a[1][tot + 1];
		for (int i = 1; i <= tot+1; i++)nans -= ans.a[1][i];
		printf("%llu\n", nans);
	}
	return 0;
}

矩阵是真的难写难调,来自一个调了2h的菜鸡
在这里插入图片描述

G - Wireless Password

题目链接: HDU - 2825
题意:给定 m m m个单词,求长 n n n并且包含至少 k k k个单词的串数量
数据范围: 1 ≤ n ≤ 25 , 0 ≤ m ≤ 10 , k ≤ m 1\le n \le 25,0\le m\le 10,k\le m 1n25,0m10,km
题解:AC自动机+状压 d p dp dp d p [ i ] [ j ] [ s t ] dp[i][j][st] dp[i][j][st]表示长 i i i到达自动机 j j j节点时状态为 s t st st的串数量。 a n s = ∑ j = 1 t o t ∑ s t d p [ n ] [ j ] [ s t ] ans=\sum_{j=1}^{tot}\sum_{st} dp[n][j][st] ans=j=1totstdp[n][j][st]
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 25 + 10;
const int mod = 20090717;
int nxt[N*10][26],fail[N * 10],st[N * 10],tot=1;
void insert(char* s,int idt)
{
	int n = strlen(s); int now = 1;
	for (int i = 0; i < n; i++)
	{
		if (!nxt[now][s[i] - 'a'])nxt[now][s[i] - 'a'] = ++tot;
		now = nxt[now][s[i] - 'a'];
	}
	st[now] |= 1 << (idt - 1);
}
void build()
{
	queue<int>pls;
	for (int i = 0; i < 26; i++)
	{
		if (nxt[1][i])pls.push(nxt[1][i]),fail[nxt[1][i]]=1;
		else
			nxt[1][i] = 1;
	}
	while (pls.size())
	{
		int f = pls.front(); pls.pop();
		for (int i = 0; i < 26; i++)
		{
			if (nxt[f][i])
			{
				fail[nxt[f][i]] = nxt[fail[f]][i];
				pls.push(nxt[f][i]);
				st[nxt[f][i]] |= st[fail[nxt[f][i]]];
			}
			else
				nxt[f][i] = nxt[fail[f]][i];
		}
	}
}
int n, m, k, dp[N][105][(1 << 10)+10];
char s[N];
void init()
{
	memset(dp, 0, sizeof(dp));
	for (int i = 1; i <= tot; i++)memset(nxt[i], 0, sizeof(nxt[i])),fail[i]=0,st[i]=0;
	tot = 1;
}
int calc(int x)
{
	int ans = 0;
	while (x)
	{
		x -= (x & -x); ans++;
	}
	return ans;
}
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	while (scanf("%d%d%d", &n, &m, &k) != EOF)
	{
		if (!n && !m && !k)break;
		init();
		for (int i = 1; i <= m; i++)
		{
			scanf("%s", s + 1); insert(s + 1, i);
		}
		build();
		int mx = (1 << m) - 1;
		dp[0][1][0] = 1;
		for(int i=0;i<=n-1;i++)
			for (int j = 1; j <= tot; j++)
			{
				for (int nt = 0; nt <= mx; nt++)
				{
					if (!dp[i][j][nt])continue;
					for (int nx = 0; nx < 26; nx++)
					{
						int to = nxt[j][nx];
						dp[i + 1][to][st[to] | nt] = (dp[i + 1][to][st[to] | nt] + dp[i][j][nt]) % mod;
					}
				}
			}
		int ans = 0;
		for (int i = 1; i <= tot; i++)
			for (int j = 0; j <= mx; j++)
				if (calc(j) >= k)ans = (ans + dp[n][i][j])%mod;
		printf("%d\n", ans);
	}

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值