【BZOJ 1030】文本生成器

1.题目链接。给定n个字符串,让你构造一个由m个字符组成的字符串并且至少包含前n个字符串中的一个,有多少种构造方案。

2.首先是一个多模式匹配的问题,对输入的字符串建立AC自动机,然后开始DP。由于正面考虑有很多种情况,我们发面考虑,不含他们中的任意一个。dp[i][j]:考虑第i位字符当前位置在AC自动机上的第j个节点时,不包含任意一个字符串的方案书。然后向i+1转移,转移时,我们需要枚举j的儿子k,对于k我们判断是不是可以把它放在第i+1个位置,显然,我们需要直到加入k之后,是不是会让前边的东西出现n个单词中的一种,这个时候,我们从当前节点,沿着fail指针向上跳,如果发现有节点的有k儿子,并且标记是真,那么k不能被加入,否则可以被加入,如果可以被加入,那么找到具有这个k儿子但是没有标记的地方(找的方式依然是沿着fail指针向上跳),那么dp[i][j]将会更新这个地方的值。最后的答案就是考虑m个字符,所有节点值的和。然后总方案数减去就是答案。

#include<bits/stdc++.h>
#define md 10007
#define ll long long 
using namespace std;
char ch[1000];
int fail[6010], dp[1200][6010];
int tr[6010][30], trcnt, danger[6010], n, m, q[6010];
void insert()   
{
	int now = 1, len = strlen(ch + 1);
	for (int k = 1; k <= len; k++)
	{
		int y = ch[k] - 'A' + 1;
		if (tr[now][y]) now = tr[now][y];
		else now = tr[now][y] = ++trcnt;
	}
	danger[now] = 1;
}
void acmach()   
{
	int head = 0, tail = 0, now;
	q[++tail] = 1;
	while (head < tail)
	{
		now = q[++head];
		for (int k = 1; k <= 26; k++)
			if (tr[now][k])
			{
				int y = fail[now];
				while (!tr[y][k]) y = fail[y];
				fail[tr[now][k]] = tr[y][k];
				q[++tail] = tr[now][k];
			}
	}
}
ll qpow(ll a, ll b)
{
	ll res = 1;
	while (b)
	{
		if (b & 1)res = res * a % md;
		a = a * a % md;
		b >>= 1;
	}
	return res;
}
int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 0; i <= 26; i++)tr[0][i] = 1;
	trcnt = 1;
	for (int i = 1; i <= n; i++)
	{
		scanf("%s", ch + 1);
		insert();
	}
	acmach();
	dp[0][1] = 1;
	for (int i = 0; i <= m - 1; i++)
	{
		for (int j = 1; j <= trcnt; j++)
		{
			for (int k = 1; k <= 26; k++)
			{
				int now = j;
				int flag = 0;
				while (now)
				{
					if (danger[tr[now][k]] == 1)
					{
						flag = 1;
						break;
					}
					now = fail[now];
				}
				if (flag == 1)continue;
				now = j;
				while (!tr[now][k])now = fail[now];
				now = tr[now][k];
				dp[i + 1][now] = (dp[i + 1][now] + dp[i][j]) % md;
			}
		}
	}
	ll res = qpow(26, m);
	ll ans = 0;
	for (int i = 1; i <= trcnt; i++)
	{
		ans = ans + dp[m][i];
		ans %= md;
	}
	ans = (res - ans + md) % md;
	cout << ans << endl;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值