考虑如果暴力 DP 的话,时间复杂度会超标,于是矩阵加速。
但是在 DP 的过程中,期望又要乘又要加的,用矩阵很难转移。于是考虑用矩阵去算概率,新开一行去存期望。
这个贪心是很显然的:当匹配完了一个单词时,直接从头开始尝试匹配下一个单词。由于题目要求不能重叠,这不仅是策略上的优化还是正确性的保证。
具体地,假设要从节点 \(j\) 转移到 \(k\),如果 \(k\) 是某个单词的结尾,那就把贡献加给根,同时加给期望。否则就只能加给 \(k\) 了。
时间复杂度 \(O((\sum|T_i|)^3\log len)\)。
#include<bits/stdc++.h>
#define ll long long
#define il inlineusing namespace std;
namespace asbt{
namespace cplx{bool begin;}
int n,m,tr[80][30];
int tot,fail[80];
long double ab;
bool end[80];
string s;
queue<int> q;
struct juz{long double mat[80][80];juz(){for(int i=0;i<=tot;i++){for(int j=0;j<=tot;j++){mat[i][j]=0;}}}il long double*operator[](int x){return mat[x];}il juz operator*(juz x)const{juz res;for(int i=0;i<=tot;i++){for(int j=0;j<=tot;j++){for(int k=0;k<=tot;k++){res[i][j]+=mat[i][k]*x[k][j];}}}return res;}
}bas;
il juz qpow(juz x,int y){juz res;for(int i=0;i<=tot;i++){res[i][i]=1;}while(y){if(y&1){res=res*x;}y>>=1,x=x*x;}return res;
}
namespace cplx{bool end;il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){ios::sync_with_stdio(0),cin.tie(0);cin>>n>>m>>ab;for(int i=1,p;i<=n;i++){cin>>s;p=0;for(int j=0,d;j<s.size();j++){d=s[j]-'a';if(!tr[p][d]){tr[p][d]=++tot;}p=tr[p][d];}end[p]=1;}for(int i=0;i<ab;i++){if(tr[0][i]){q.push(tr[0][i]);}}while(q.size()){int u=q.front();q.pop();for(int i=0;i<ab;i++){if(tr[u][i]){fail[tr[u][i]]=tr[fail[u]][i];end[tr[u][i]]|=end[fail[tr[u][i]]];q.push(tr[u][i]);}else{tr[u][i]=tr[fail[u]][i];}}}
// for(int i=0;i<=tot;i++){
// cout<<fail[i]<<" ";
// }
// puts("");tot++;for(int i=0;i<tot;i++){for(int j=0;j<ab;j++){if(end[tr[i][j]]){bas[i][0]+=1.0l/ab;bas[i][tot]+=1.0l/ab;}else{bas[i][tr[i][j]]+=1.0l/ab;}}}bas[tot][tot]=1;printf("%.10Lf",qpow(bas,m)[0][tot]);return 0;
}
}
int main(){return asbt::main();}