• 【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;//矩乘输出答案
    }
    
  • 相关阅读:
    优秀大数据GitHub项目一览
    自定义组件-BreadcrumbTreeView 的使用
    IOS中的属性列表----Property List
    即时通讯之smack客户端配置
    Android studio 使用问题汇总
    触摸事件UITouch的应用
    Android界面设计之对话框——定制Toast、AlertDialog
    android_orm框架之greenDAO(一)
    火速提升Android仿真器的运行速度 ——仿真器Genymotion
    Android 中的缓存机制与实现
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ2553.html
Copyright © 2020-2023  润新知