【luogu P4052】[JSOI2007]文本生成器

91 篇文章 0 订阅
10 篇文章 0 订阅

[JSOI2007]文本生成器

题目链接:luogu P4052

题目大意

一个东西会生成长度为 m 的由大写字符组成的字符串,然后问你生成的所有字符串中,有多少个字符串是可读的。
可读的仅当至少某些规定的字符串的其中一个在这个字符串出现过。

思路

这道题,看到要跟很多个字符串匹配,自然想到用 AC 自动机。

但是用脚想一想都知道不能直接暴力一个一个看。
那怎么办呢?
求出现过很麻烦,那我们不如找不可读的数量,然后把全部减去不可读的数量。

那怎么求不可读的呢?
就是构建出来的字符串里面不能出现给定的字符串。
那我们就在 Trie 树里面搞一个 bool 值记录这个字符串是不是可读的。
(也就是说,如果是真,就是可读的,我们算不可读的数量的时候就不能选它)

那怎么求出整个 Trie 树里面的这个 bool 值呢?
那我们可以想到,如果一个位置跳 fail 边,跳到的位置的这个 bool 值是真,那这个位置也应该是真。

那我们就可以在构建 fail 边的时候把这个值求出来。

接着,我们考虑用 dp 求不可读的字符串数量。
f i , j f_{i,j} fi,j 表示当前 i i i 位的字符串,然后匹配到 Trie 树上的节点 j j j 时,不可读的字符串数量是多少。
那我们就枚举当前长度当前点的二十六个儿子,如果儿子的 bool 值不是真,就让长度加一位置是这个点的这个儿子的 dp 值加上你这个位置的 dp 值。
说的很乱,其实就是 f i + 1 , s o n j , k = f i + 1 , s o n j , k + f i , j f_{i+1,son_{j,k}}=f_{i+1,son_{j,k}}+f_{i,j} fi+1,sonj,k=fi+1,sonj,k+fi,j。(前提是 c a n t s o n j , k = = 0 cant_{son_{j,k}} == 0 cantsonj,k==0

然后就枚举长度,节点,以及哪个儿子,dp 一下。

最后我们只要用全部字符串的数量减去 ∑ i = 0 K K d p m , i \sum\limits^{KK}_{i=0}dp_{m,i} i=0KKdpm,i K K KK KK 是 Trie 树的节点数,其实就是减去最后每一种结束情况的字符串数量),就是答案了。

至于全部字符串怎么求,就直接 2 6 m 26^m 26m 啊。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#define mo 10007

using namespace std;

struct Trie {
	int son[31], fail, num;
	bool cant;
}tree[6001];
int n, m, size[61], thi, now, dp[101][6001], ans, KK;
char c[61][101];
queue <int> q;

void build_Trie(int thistime) {
	now = 0;
	for (int i = 0; i < size[thistime]; i++) {
		thi = c[thistime][i] - 'A';
		if (!tree[now].son[thi]) tree[now].son[thi] = ++KK;
		now = tree[now].son[thi];
	}
	
	tree[now].cant = 1;
}

void get_fail() {
	for (int i = 0; i < 26; i++)
		if (tree[0].son[i]) {
			tree[tree[0].son[i]].fail = 0;
			q.push(tree[0].son[i]);
		}
	
	while (!q.empty()) {
		now = q.front();
		q.pop();
		
		for (int i = 0; i < 26; i++) {
			if (tree[now].son[i]) {
				tree[tree[now].son[i]].fail = tree[tree[now].fail].son[i];
				q.push(tree[now].son[i]);
				tree[tree[now].son[i]].cant |= tree[tree[tree[now].son[i]].fail].cant;
				//如果它fail边连着的点已经是不行,那这个点也不行
			}
			else tree[now].son[i] = tree[tree[now].fail].son[i];
		}
	}
}

void ksm(int x, int y) {
	ans = 1;
	while (y) {
		if (y & 1) ans = (ans * x) % mo;
		x = (x * x) % mo;
		y >>= 1;
	}
}

int main() {
	scanf("%d %d", &n, &m);
	
	for (int i = 1; i <= n; i++) {
		scanf("%s", &c[i]);
		size[i] = strlen(c[i]);
		build_Trie(i);
	}
	
	get_fail();
	
	dp[0][0] = 1;//dp求出方案
	for (int i = 0; i < m; i++) {
		for (int j = 0; j <= KK; j++)
			for (int k = 0; k < 26; k++)
				if (!tree[tree[j].son[k]].cant)
					dp[i + 1][tree[j].son[k]] = (dp[i + 1][tree[j].son[k]] + dp[i][j]) % mo;
	}
	
	ksm(26, m);
	
	for (int i = 0; i <= KK; i++)
		ans = ((ans - dp[m][i]) % mo + mo) % mo;
	
	printf("%d", ans);
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值