先考虑一个串A如何划分价值最大,只需要按照所有T串在A中匹配的右端点排个序贪心去选,也就是希望我在A中匹配i个禁忌串,最靠后的右端点应该尽量靠前。
在AC自动机上对应为:只要走到一个禁忌串的终止节点,就将它划分出一段,(这里的终止节点包括那些顺着fail指针能走到终止节点的点)。
可以设dp[ i ][ j ]为在AC自动机上走了i步,走到了j节点的概率。
dp[ i ] [ ch[j][k] ] += dp[ i-1 ][ j ]*(1/alphabet);
dp[ i ] [ root ] += dp[ i-1 ][ j ]*1 ;(若果 j 是T串的终止节点)。
答案应该怎样统计,可以新建一个节点N,每当向root转移时,也同样向N点转移,最后 dp[ len ][ N ]即为答案。
可以用矩阵乘法优化。
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 105
#define flt long double
using namespace std;
char s[maxn];
int k,N;
struct MAT{
flt a[maxn][maxn];
void print()
{
for(int i=0;i<=N;i++)
{
for(int j=0;j<=N;j++)
cout<<a[i][j]<<" ";
puts("");
}
}
};
MAT zero;
MAT* operator *(MAT &A,MAT &B)
{
MAT *C=&zero;
for(int i=0;i<=N;i++)
for(int j=0;j<=N;j++)
C->a[i][j]=0;
for(int i=0;i<=N;i++)
for(int j=0;j<=N;j++)
for(int k=0;k<=N;k++)
C->a[i][j]+=A.a[i][k]*B.a[k][j];
return C;
}
MAT A,ans;
void pow(int y)
{
for(int i=0;i<=N;i++)
ans.a[i][i]=1;
for(;y;y>>=1)
{
if(y&1)ans=*(ans*A);
A=*(A*A);
}
}
struct Trie
{
int fail[maxn];
bool val[maxn];
int ch[maxn][30],cnt;
void insert()
{
int p=0;
int l=strlen(s+1);
for(int i=1;i<=l;i++)
{
int c=s[i]-'a'+1;
if(!ch[p][c]) ch[p][c]=++cnt;
p=ch[p][c];
}
val[p]=1;
}
queue<int> Q;
void build()
{
for(int i=1;i<=k;i++)
if(ch[0][i]) Q.push(ch[0][i]);
while(!Q.empty())
{
int r=Q.front();Q.pop();
for(int i=1;i<=k;i++)
{
int u=ch[r][i];
if(!u)
{
ch[r][i]=ch[fail[r]][i];
continue;
}
Q.push(u);
int p=fail[r];
while(p&&!ch[p][i]) p=fail[p];
fail[u]=ch[p][i];
val[u]|=val[fail[u]];
}
}
}
void get_mat()
{
N=cnt+1;
flt x=1/(flt)k;
for(int i=0;i<=cnt;i++)
{
if(val[i]) continue;
for(int j=1;j<=k;j++)
{
int u=ch[i][j];
if(val[u])
{
A.a[i][N]+=x;
A.a[i][0]+=x;
}
else A.a[i][u]+=x;
}
}
A.a[N][N]=1;
}
}AC;
MAT B;
int main()
{
int n,len;
scanf("%d%d%d",&n,&len,&k);
for(int i=1;i<=n;i++)
{
scanf("%s",s+1);
AC.insert();
}
AC.build();
AC.get_mat();
B.a[0][0]=1;
pow(len);
B=*(B*ans);
double q=B.a[0][N];
printf("%.9lf",q);
return 0;
}