洛谷 4052 loj 10063 bzoj 1030 [JSOI2007]文本生成器 题解

本文探讨了一个字符串问题的求解方法,利用AC自动机和动态规划(DP)来计算特定条件下字符串的数量。具体地,文章介绍了如何通过构建AC自动机并运行DP算法,来找出长度为m且至少包含一个给定单词的字符串数量,同时考虑到1e4+7的模数限制。

博客观赏效果更佳

题意简述

你要求有多少个字符串,使得:

  1. 长度为m
  2. 包含至少一个给定的单词。会给定 n n n个单词。

1 e 4 + 7 1e4+7 1e4+7

思路框架

用总共的方案数减去一个单词都不包含的方案数。前面那个是 2 6 n 26^n 26n,后面那个在 A C AC AC自动机上跑 D P DP DP求解。

具体思路

首先,“至少一个”->“总共减去一个都没有”,是一个经典套路。这个不多说。

然后讲讲如何 d p dp dp。设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示,长度为 i i i,匹配到 A C AC AC自动机的第 j j j个位置。在建立 f a i l fail fail然后若合法,那么就从 d p [ i ] [ j ] dp[i][j] dp[i][j]转移到 d p [ i + 1 ] [ s o n ] dp[i+1][son] dp[i+1][son]。最后的答案是所有 d p [ m ] [ i ] dp[m][i] dp[m][i]的和。

实现注意

  1. AC自动机别写挂了
  2. 多开点空间,别怂

代码

#include<bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
	#define N 14444
	#define mod 10007
	#define F(i,l,r) for(int i=l;i<=r;++i)
	#define D(i,r,l) for(int i=r;i>=l;--i)
	#define Fs(i,l,r,c) for(int i=l;i<=r;c)
	#define Ds(i,r,l,c) for(int i=r;i>=l;c)
	#define Tra(i,u) for(int i=G.Start(u),__v=G.To(i);~i;i=G.Next(i),__v=G.To(i))
	#define MEM(x,a) memset(x,a,sizeof(x))
	#define FK(x) MEM(x,0)

	bool cxk[N];
	class Ah_Chtholly_Automaton
	{
	public:
		int tr[N][26];
		int tot;
		void Init()
		{
			FK(tr);
			tot=0;
		}
		int Insert(char s[])
		{
			int pos=0;
			for(int i=0;s[i];++i)
			{
				int c=s[i]-'A';
				if (!tr[pos][c])
				{
					tr[pos][c]=++tot;
				}
				pos=tr[pos][c];
			}
			cxk[pos]=1;
			return pos;
		}

		int fail[N];
		queue<int> Q;
		void BuildFail()
		{
			FK(fail);
			while(!Q.empty()) Q.pop();

			F(i,0,25) 
			{
				if (tr[0][i]) Q.push(tr[0][i]);
			}
			while(!Q.empty())
			{
				int u=Q.front();Q.pop();

				F(i,0,25)
				{
					if (tr[u][i])
					{
						fail[tr[u][i]]=tr[fail[u]][i];
						cxk[tr[u][i]]|=cxk[fail[tr[u][i]]];
                                                //是否合法的判断:如果fail不合法,那我也不合法
						Q.push(tr[u][i]);
					}
					else tr[u][i]=tr[fail[u]][i];
				}
			}
		}
	}AC;

	int n,m;
	int id[N];
	char tmp[N];
	void Input()
	{
		scanf("%d%d",&n,&m);
		AC.Init();
		F(i,1,n)
		{
			scanf("%s",tmp);
			id[i]=AC.Insert(tmp);
		}
	}

	int dp[110][N];
	int qpow(int a,int b,int m)
	{
		int r=1;
		while(b)
		{
			if (b&1) r=r*a%m;
			a=a*a%m,b>>=1;
		}
		return r;
	}
	void Soviet()
	{
		AC.BuildFail();
		dp[0][0]=1;
		F(i,0,m-1) F(j,0,AC.tot) F(k,0,25)
		{
			int ch=AC.tr[j][k];
			if (!cxk[ch])
			{
				dp[i+1][ch]+=dp[i][j];
				dp[i+1][ch]%=mod;
			}
		}

		int ans=qpow(26,m,mod);
		F(i,0,AC.tot) 
		{
			ans=(ans-dp[m][i]+mod)%mod;
		}
		printf("%d\n",ans);	
	}
	void IsMyWife()
	{
		Input();
		Soviet();
	}
}
int main()
{
	Flandre_Scarlet::IsMyWife();
	getchar();getchar();
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值