用高斯消元解方程组即可, 因为 AC 自动机的结点数为 O(nm) 级别,时间复杂度 O(n3m3)。
Part 2
上一个做法的问题在于未知量的数量过多,实际上我们并不关心那些未被标记过的结点的 E 值,而被标记过的结点只有 O(n) 级别。
考虑设 Ei 表示经过 AC 自动机上第 i 个被标记过的结点的期望次数,另外记 E0 表示经过 AC 自动机上未被标记的结点的期望次数的和。
现在我们需要根据这些变量的关系列出 n+1 个方程,最后同样用高斯消元解出答案。
注意到 Ei 的实际意义是第 i 个人获胜的概率。尽管我们也许能够构造出一个无穷长的序列使得没有人获胜,但这样必须不停地抛硬币,概率无限趋近于0。于是我们可以得到第一个方程:i=1∑nEi=1
考虑从 AC 自动机上一个未被标记的结点出发,按照第 i 个人给定的硬币序列,依次经过 m 条转移边,如果不考虑在这一过程中已经经过了被标记过的点而导致游戏结束,最后一定能走到第 i 个被标记过的结点,即 Ei=2m1E0(每走一步的概率都是 21)。
现在要扣除那些不合法的情况(这也正是 Ei 可能互不相同的原因),考虑枚举这一过程中经过的第一个被标记过的点,假定它是第 j 个被标记的点(j 可以等于 i),那么一定存在一个 k(1≤k<m) 使得第 j 个硬币序列长度为 k 的后缀等于第 i 个硬币序列长度为 k 的前缀,据此可以对于每一个 i 列出一个方程:Ei+j=1∑n(k=1∑m−1[pre(i,k)=suf(j,k)]2m−k1)Ej−2m1E0=0
其中 pre(i,k) 即表示第 i 个硬币序列长度为 k 的前缀,suf(j,k) 同理。
实现时可以通过哈希暴力判断前后缀相等,时间复杂度 O(n3)。
Code
#include<bits/stdc++.h>constint mod1 =1e9+7;constint mod2 =1e9+9;constint N =305;char s[N][N];double ex[N], f[N][N];int a[N], p1[N], p2[N];int pre1[N][N], pre2[N][N], suf1[N][N], suf2[N][N];int n, m;intmain(){scanf("%d%d",&n,&m);for(int i =1; i <= n;++i)scanf("%s", s[i]+1);
ex[0]=1;for(int i =1; i <= m;++i)
ex[i]=0.5* ex[i -1];
p1[0]= p2[0]=1;for(int i =1; i <= m;++i){
p1[i]=29ll* p1[i -1]% mod1;
p2[i]=31ll* p2[i -1]% mod2;}for(int i =1; i <= n;++i){for(int j =1; j <= m;++j)
a[j]= s[i][j]=='T'?5:7;for(int j =1; j <= m;++j){
pre1[i][j]=(29ll* pre1[i][j -1]+ a[j])% mod1;
pre2[i][j]=(31ll* pre2[i][j -1]+ a[j])% mod2;}for(int j =1; j <= m;++j){int tmp = m - j +1;
suf1[i][j]=(1ll* p1[j -1]* a[tmp]+ suf1[i][j -1])% mod1;
suf2[i][j]=(1ll* p2[j -1]* a[tmp]+ suf2[i][j -1])% mod2;}}for(int i =1; i <= n;++i){
f[i][i]=1.0;for(int j =1; j <= n;++j)for(int k =1; k < m;++k)if(suf1[j][k]== pre1[i][k]&& suf2[j][k]== pre2[i][k])
f[i][j]+= ex[m - k];
f[i][n +1]-= ex[m];}for(int i =1; i <= n;++i)
f[n +1][i]=1.0;
f[n +1][n +2]=1.0;++n;for(int i =1; i <= n;++i){int p = i;for(int j = i +1; j <= n;++j)if(fabs(f[p][i])<fabs(f[j][i]))
p = j;if(p != i){for(int j = i; j <= n +1;++j)
std::swap(f[p][j], f[i][j]);}double tmp = f[i][i];for(int j = i; j <= n +1;++j)
f[i][j]/= tmp;for(int j =1; j <= n;++j)if(j != i){
tmp = f[j][i];for(int k = i; k <= n +1;++k)
f[j][k]-= tmp * f[i][k];}}for(int i =1; i < n;++i)printf("%.10lf\n", f[i][n +1]);}