Vexoben
Aug 14, 2018
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2553
题解
总算是见识了一下AC自动机上跑DP。
将所有禁忌串加到AC自动机里。需要注意的是,如果一个串是另一串的前缀,较长的串一定不会被加入。
用f[i][j]表示走i步,恰好走到j时答案的期望。
转移很好考虑,难点在于统计答案。
如果ch[i][j]是一个禁忌串的结尾节点,那么将它转移到根节点和一个虚构的节点x。否则将它转移到ch[i][j]。统计答案的时候只需要统计转移矩阵的len次幂上(rt,x)的值就可以了。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,len,bet;
struct Matrix{
int n,m;
long double a[105][105];
void Init() {
n=m=0;
memset(a,0,sizeof(a));
}
}E;
Matrix operator * (Matrix x,Matrix y) {
Matrix z; z.Init();
z.n=x.n; z.m=y.m;
for (int i=0;i<=z.n;i++) {
for (int j=0;j<=z.m;j++) {
for (int k=0;k<=x.m;k++) {
z.a[i][j]+=x.a[i][k]*y.a[k][j];
}
}
}
return z;
}
namespace AC {
char s[N];
int rt=0,tot=0;
int ch[N][26],tag[N],fail[N];
void insert() {
scanf("%s",s+1); int l=strlen(s+1);
int now=rt;
for (int i=1;i<=l;i++) {
if (tag[now]) return;
if (!ch[now][s[i]-'a']) ch[now][s[i]-'a']=++tot;
now=ch[now][s[i]-'a'];
}
tag[now]=1;
}
void Getfail() {
queue<int> Q;
fail[rt]=rt;
for (int i=0;i<bet;i++) {
if (ch[rt][i]) fail[ch[rt][i]]=rt,Q.push(ch[rt][i]);
}
while (!Q.empty()) {
int x=Q.front(); Q.pop();
for (int i=0;i<bet;i++) {
if (ch[x][i]) {
fail[ch[x][i]]=ch[fail[x]][i];
Q.push(ch[x][i]);
}
else ch[x][i]=ch[fail[x]][i];
}
}
}
}
using namespace AC;
int main() {
scanf("%d%d%d",&n,&len,&bet);
for (int i=1;i<=n;i++) insert();
Getfail();
E.n=E.m=tot;
E.a[tot][tot]=1.0;
for (int i=0;i<tot;i++) {
for (int j=0;j<bet;j++) {
if (tag[ch[i][j]]) {
E.a[i][rt]+=1.0/bet;
E.a[i][tot]+=1.0/bet;
}
else E.a[i][ch[i][j]]+=1.0/bet;
}
}
Matrix ans; ans.Init();
ans.n=ans.m=tot;
for (int i=0;i<=tot;i++) ans.a[i][i]=1;
while (len) {
if (len&1) ans=ans*E;
E=E*E; len>>=1;
}
printf("%.10Lf",ans.a[0][tot]);
return 0;
}