把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ2553】[BeiJing2011] 禁忌(AC自动机+矩乘)

点此看题面

大致题意: 给定\(n\)个禁忌串,随机生成一个长度为\(m\)、由前\(alphabet\)种小写字母构成的字符串,把它划分成若干段并最大化其中是禁忌串的段数,求这个段数的期望。

\(AC\)自动机

这种多模匹配问题,一看就要先建个\(AC\)自动机出来。

然后我们发现,最大化段数显然就是在一出现禁忌串时就将答案加\(1\),并返回根节点。

那么我们只要在建\(AC\)自动机时预处理出每个节点是否以某个禁忌串为后缀,就可以开心地在\(AC\)自动机上\(DP\)了。

我们设\(f_{i,j}\)表示从根节点出发第\(i\)步走到\(j\)节点的概率,显然对于每个节点只需要枚举其后继状态就可以得出转移:

  • 若该后继状态以某个禁忌串为后缀,转移到根节点,并将答案加上这一概率。
  • 否则,直接转移到该后继状态。

转移系数就是\(\frac1{alphabet}\)

然后我们发现长度\(m\)很大,于是自然而然想到矩乘。

但如何统计答案呢?

可以发现答案只会在返回根节点时更新,因此只要注意增开第\(0\)行/列统计到根节点的概率总和即可。

具体实现详见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define DB long double
using namespace std;
int n,m,Mx,Nt;char s[N+5];
struct M
{
	DB a[N+5][N+5];I M() {memset(a,0,sizeof(a));}I DB *operator [] (CI x) {return a[x];}
	I M operator * (Con M& o) Con//矩乘
	{
		M t;RI i,j,k;for(i=0;i<=Nt;++i) for(j=0;j<=Nt;++j)
			for(k=0;k<=Nt;++k) t[i][j]+=a[i][k]*o.a[k][j];return t;
	}
	I M operator ^ (RI y) Con//矩阵快速幂
	{
		M x=*this,t;for(RI i=0;i<=Nt;++i) t[i][i]=1;W(y) y&1&&(t=t*x,0),x=x*x,y>>=1;return t;
	}
}U;
class AcAutomation//AC自动机
{
	private:
		int q[N+5],vis[N+5];struct node {int V,F,S[30];}O[N+5];
	public:
		I void Ins(char *s)//插入字符串
		{
			RI i,x=1,t,l=strlen(s+1);for(i=1;i<=l;++i)
				!O[x].S[t=s[i]&31]&&(O[x].S[t]=++Nt),x=O[x].S[t];O[x].V=1;
		}
		I void Build()//建AC自动机
		{
			RI i,k,H=1,T=0;for(i=1;i<=Mx;++i) (O[1].S[i]?O[q[++T]=O[1].S[i]].F:O[1].S[i])=1;
			W(H<=T) for(k=q[H++],i=1;i<=Mx;++i) O[k].S[i]?//同时维护是否以某禁忌串为后缀
				O[q[++T]=O[k].S[i]].F=O[O[k].F].S[i],O[O[k].S[i]].V|=O[k].V:O[k].S[i]=O[O[k].F].S[i];
		}
		I void GetU(CI x=1)//得出转移矩阵
		{
			if(vis[x]) return;for(RI i=vis[x]=1;i<=Mx;++i) O[O[x].S[i]].V?//枚举后继状态
				(U[x][1]+=1.0L/Mx,U[x][0]+=1.0L/Mx):(U[x][O[x].S[i]]+=1.0L/Mx,GetU(O[x].S[i]),0);//分两种情况讨论
		}
}AC;
int main()
{
	RI i;for(Nt=1,scanf("%d%d%d",&n,&m,&Mx),i=1;i<=n;++i) scanf("%s",s+1),AC.Ins(s);
	return AC.Build(),AC.GetU(),U[0][0]=1,printf("%.10Lf",(U^m)[1][0]),0;//矩乘输出答案
}
posted @ 2020-05-31 19:51  TheLostWeak  阅读(120)  评论(0编辑  收藏  举报