bzoj 2553 [BeiJing2011]禁忌——AC自动机+概率DP+矩阵
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2553
看了题解才会……
首先,给定一个串,最好的划分方式是按禁忌串出现的右端点排序,遇到能填的就填上。在 AC 自动机上就是一旦能走到一个禁忌串的终止节点,就 ans++ 并走到根去。
考虑怎么把 ans++ 也体现在矩阵乘法里。而且还要期望……
只要在矩阵里填上概率,最后就能算出期望了。体现 ans++ 的话,就是在 “从当前节点到根” 的同时给 “从当前节点到 tot ” 的概率也加上 \( \frac{1}{alphabet} \) 即可。
最后就看一下乘了 len 次之后从根走到 tot 点的值即可。
听说要开 long double 。
#include<cstdio> #include<cstring> #include<algorithm> #define db long double using namespace std; const int N=80,K=30; int n,m,alp,tot=1,c[N][K],fl[N],q[N]; db p; char ch[N]; bool en[N]; struct Mtr{ db a[N][N]; Mtr(){for(int i=1;i<=tot;i++)for(int j=1;j<=tot;j++)a[i][j]=0;}; Mtr operator* (const Mtr &b)const { Mtr c; for(int i=1;i<=tot;i++) for(int k=1;k<=tot;k++) for(int j=1;j<=tot;j++) c.a[i][j]+=a[i][k]*b.a[k][j]; return c; } }t,ans; void get_fl() { int he=0,tl=0; for(int j=0;j<alp;j++) if(c[1][j])q[++tl]=c[1][j],fl[c[1][j]]=1; else c[1][j]=1; while(he<tl) { int k=q[++he]; if(en[fl[k]])en[k]=1; for(int j=0;j<alp;j++) { if(c[k][j]) { int cr=fl[k]; while(cr&&!c[cr][j])cr=fl[cr]; if(c[cr][j])fl[c[k][j]]=c[cr][j]; else fl[c[k][j]]=1; q[++tl]=c[k][j]; } else { int cr=fl[k]; while(cr&&!c[cr][j])cr=fl[cr]; if(c[cr][j])c[k][j]=c[cr][j]; else c[k][j]=1; } } } } int main() { scanf("%d%d%d",&n,&m,&alp); p=1.0/alp; for(int i=1;i<=n;i++) { scanf("%s",ch+1); int d=strlen(ch+1), cr=1; for(int j=1;j<=d;j++) { int w=ch[j]-'a'; if(!c[cr][w])c[cr][w]=++tot; cr=c[cr][w]; } en[cr]=1; } get_fl(); tot++; t.a[tot][tot]=1; for(int i=1;i<tot;i++) for(int j=0;j<alp;j++) { if(en[c[i][j]]){ t.a[i][1]+=p; t.a[i][tot]+=p;} else t.a[i][c[i][j]]+=p; } ans=t; m--; while(m) { if(m&1)ans=ans*t; t=t*t;m>>=1; } printf("%.10Lf\n",ans.a[1][tot]); return 0; }