bzoj 1030 文本生成器(AC+DP)

题意:给几个模式串,找26^m次方个串中出现这些模式串的个数。(即方案数。

题解:逆向思维,算是一个简单的容斥原理,找到所有不含有模式串的个数,剪掉。方法就是f[i][j]来统计在第i位(i<=m)时,匹配到第j个结点的方案数。最后求和,做差就好了。

起初总想不明白如何转移的,最后想想它是把26个字母都遍历一遍进行转移,就差不多。

转移方程:f[i+1][v] = (f[i+1][v] + f[i][j])%mod。v见下面的代码。转移的话,就是之前的方案数,加上这一次的方案数,同一个i的循环中有时会很多跳回之前的结点,从而递增。例如i == 3,由于失配指针,出现了f[4][0]了多次(例子与题目无关)从而递加。

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 10010,alpha = 26,maxm = 110,mod = 10007;

int f[maxn],cnt[maxm][maxn],pc,ch[maxn][alpha]; 
char ptr[maxm];
bool flag[maxn];

void ins(char *s)
{
	int cur = 0,n = strlen(s);
	for (int i=0;i<n;i++)
	{
		int c = s[i] - 'A';
		if (!ch[cur][c]) ch[cur][c] = ++pc;
		cur = ch[cur][c];
	}
	flag[cur] = true;
}
void getfail()
{
	queue<int> q;
	for (int c=0;c<alpha;c++)
	if (ch[0][c]) q.push(ch[0][c]);
	while (!q.empty())
	{
		int r = q.front();q.pop();
		for (int c=0;c<26;c++)
		{
			int u = ch[r][c];
			if(!u) continue;
			q.push(u);
			int j = f[r];
			while (!ch[j][c] && j) j = f[j];
			j = ch[j][c];
			f[u] = j;
			if (flag[f[u]]) flag[u] = true;	  
			//这一个很关键,他转移过去的模式串出现了在文本中,所以他也就不能被dp转移	
		}
	}
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	while(n--)
	{
		scanf(" %s",ptr);           
		//scanf中的空格据大佬说可以避免不同系统的不同结束情况引起的错误,如\n,\r\n。会被空格吸收 
		ins(ptr);
	}
	getfail();
	cnt[0][0] = 1;
	for (int i=0;i<m;i++)
	{
		for (int j=0;j<=pc;j++)
		{
			if (!cnt[i][j] && flag[j])  continue; 
			//cnt为0即前面有模式串出现,flag为true即当前j位出现模式串,所以跳过 
			for (int c=0;c<alpha;c++)
			{
				int tmp = j;
				while(tmp && !ch[tmp][c]) tmp = f[tmp];
				//这个是说没有子结点就跳失配指针,没有找到就是回归root结点 
				int v = ch[tmp][c];
				if (!flag[v]) cnt[i+1][v]=(cnt[i+1][v]+cnt[i][j])%mod;
			}
		}
	}
	int ans = 0,sum = 1;
	for(int i=0;i<m;++i) 
		sum = sum * 26 %mod;
	for(int i=0;i<=pc;++i){
		if(!flag[i]) ans = (ans + cnt[m][i]) % mod;
	}
	printf("%d\n",(sum - ans + mod)%mod);
	return 0;
	
}

做题时借鉴了此文:https://blog.csdn.net/zhhx2001/article/details/52137250

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值