对于特定的长度为 n n n的字符串,容易发现如果 [ l , r ] [l,r] [l,r]是一个禁忌字符串,那么答案马上可以加一
然后单独再对 [ r + 1 , n ] [r+1,n] [r+1,n]计算贡献,这样一定最优.
于是使用 a c ac ac自动机压缩一下 n n n个字符串的状态,定义 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个字符匹配到 j j j位置的概率
每次枚举字母 k k k,设状态 j j j通过字母 k k k可以转移到状态 n x t nxt nxt
当 n x t nxt nxt为终止节点时,设 p r o a l p = 1 a l p h a b e t proalp=\frac{1}{alphabet} proalp=alphabet1
f [ i + 1 ] [ 0 ] + = f [ i ] [ j ] ∗ p r o a l p f[i+1][0]+=f[i][j]*proalp f[i+1][0]+=f[i][j]∗proalp
且 a n s + = f [ i ] [ j ] ans+=f[i][j] ans+=f[i][j]
当 n x t nxt nxt不为终止节点时
f [ i + 1 ] [ n x t ] + = f [ i ] [ j ] ∗ p r o a l p f[i+1][nxt]+=f[i][j]*proalp f[i+1][nxt]+=f[i][j]∗proalp
时间复杂度 O ( l e n ∗ t o t ) O(len*tot) O(len∗tot),其中 t o t tot tot表示自动机上的点数,大概在 200 200 200左右
注意到每次的转移是一样的,于是可以用矩阵来转移
初始矩阵为
S = [ f 0 f 1 ⋯ f t o t − 1 a n s ] S= \begin{bmatrix} f_0 & f_1 & \cdots & f_{tot-1} &ans\\ \end{bmatrix} S=[f0f1⋯ftot−1ans]
其中 f i f_i fi表示当前在自动机节点 i i i的概率, a n s ans ans表示现在的答案
转移矩阵是一个 ( t o t + 1 ) ∗ ( t o t + 1 ) (tot+1)*(tot+1) (tot+1)∗(tot+1)的矩阵 z z z
当状态 j j j能通过字母 k k k转移到状态 q q q时 z [ j ] [ q ] + = p r o a l p z[j][q]+=proalp z[j][q]+=proalp
特殊的,对于最后一列是计算答案用的,设状态 j j j能通过字母 k k k转移到终止节点 w w w
那么 z [ j ] [ t o t ] + = p r o a l p z[j][tot]+=proalp z[j][tot]+=proalp
且 z [ t o t ] [ t o t ] = 1 z[tot][tot]=1 z[tot][tot]=1,这是为了继承上一轮计算的 a n s ans ans
#include <bits/stdc++.h>
using namespace std;
#define Lf long double
const int maxn =3e5+10;
int zi[maxn][30],fail[maxn],id,val[maxn];
int n,len,alp,mx;
char s[maxn];
struct rce
{
Lf a[109][109];
rce(){ memset(a,0,sizeof a ); }
rce operator * ( const rce&b ) const
{
rce s;
for(int i=0;i<=mx;i++)
for(int j=0;j<=mx;j++)
{
s.a[i][j] = 0;
for(int k=0;k<=mx;k++)
s.a[i][j] += a[i][k]*b.a[k][j];
}
return s;
}
};
rce quick(rce a,int n)
{
rce ans = a;
for( ; n ; n>>=1,a = a*a )
if( n&1 ) ans = ans*a;
return ans;
}
void insert(char s[])
{
int le = strlen( s+1 ), now = 0;
for(int i=1;i<=le;i++)
{
if( zi[now][s[i]-'a']==0 ) zi[now][s[i]-'a'] = ++id;
now = zi[now][s[i]-'a'];
}
val[now] = 1;//这个位置有字符串存在
}
void make_fail()
{
queue<int>q;
for(int i=0;i<=25;i++)
if( zi[0][i] ) q.push( zi[0][i] );
while( !q.empty() )
{
int u = q.front(); q.pop();
for(int i=0;i<=25;i++)
{
if( zi[u][i] )//存在这个节点
fail[zi[u][i]] = zi[fail[u]][i],q.push( zi[u][i] );
else
zi[u][i] = zi[fail[u]][i];
}
}
}
int main()
{
cin >> n >> len >> alp;
for(int i=1;i<=n;i++)
{
scanf("%s",s+1 );
insert( s );
}
make_fail(); mx = id+1;
Lf proalp = 1.0/alp;
rce chu, zhuan;
chu.a[0][0] = 1;
for(int q=0;q<=id;q++)//枚举状态
for(int j=0;j<alp;j++)
{
int nxt = zi[q][j];
if( val[nxt] )
{
zhuan.a[q][0] += proalp;
zhuan.a[q][id+1] += proalp;
}
else//不是终止状态
zhuan.a[q][nxt] += proalp;
}
zhuan.a[id+1][id+1] = 1;
chu = chu*quick( zhuan,len-1 );
printf("%.7Lf",chu.a[0][id+1] );
}