考虑建出 AC 自动机,则一个长度任意的字符串对应了 AC 自动机上的一条有向路径(可经过重复点)。
直观的想法是设
P
i
P_i
Pi 表示到达 AC 自动机上结点
i
i
i 的概率,最后答案即为那些被标记过的节点的
P
P
P 值。
但我们注意到,转移是会成环的,尽管我们能够用高斯消元解决成环的转移,但我们计算的概率是不能在 AC 自动机上走一圈再加回来的。
归根结底,是我们设计的状态出现了问题。
实际上应该设
E
i
E_i
Ei 表示经过 AC 自动机上结点
i
i
i 的期望次数,就允许了一个结点被经过多次,并且到达被标记过的结点之后游戏就会结束,所以被标记过的结点的
E
E
E 值在数值上就等于到达这个结点的概率。
设
G
[
i
]
[
j
]
G[i][j]
G[i][j] 表示 AC 自动机上的结点
i
i
i 经过字符为
j
j
j 的转移边到达的结点, 则对于每个未被标记过的结点
i
i
i,显然有转移:
E
G
[
i
]
[
j
]
+
=
1
2
E
i
E_{G[i][j]} += \frac{1}{2} E_i
EG[i][j]+=21Ei
用高斯消元解方程组即可, 因为 AC 自动机的结点数为
O
(
n
m
)
O(nm)
O(nm) 级别,时间复杂度
O
(
n
3
m
3
)
O(n^3m^3)
O(n3m3)。
Part 2
上一个做法的问题在于未知量的数量过多,实际上我们并不关心那些未被标记过的结点的
E
E
E 值,而被标记过的结点只有
O
(
n
)
O(n)
O(n) 级别。
考虑设
E
i
E_i
Ei 表示经过 AC 自动机上第
i
i
i 个被标记过的结点的期望次数,另外记
E
0
E_0
E0 表示经过 AC 自动机上未被标记的结点的期望次数的和。
现在我们需要根据这些变量的关系列出
n
+
1
n + 1
n+1 个方程,最后同样用高斯消元解出答案。
注意到
E
i
E_i
Ei 的实际意义是第
i
i
i 个人获胜的概率。尽管我们也许能够构造出一个无穷长的序列使得没有人获胜,但这样必须不停地抛硬币,概率无限趋近于0。于是我们可以得到第一个方程:
∑
i
=
1
n
E
i
=
1
\sum \limits_{i = 1}^{n} E_i = 1
i=1∑nEi=1
考虑从 AC 自动机上一个未被标记的结点出发,按照第
i
i
i 个人给定的硬币序列,依次经过
m
m
m 条转移边,如果不考虑在这一过程中已经经过了被标记过的点而导致游戏结束,最后一定能走到第
i
i
i 个被标记过的结点,即
E
i
=
1
2
m
E
0
E_i = \frac{1}{2^m}E_0
Ei=2m1E0(每走一步的概率都是
1
2
\frac{1}{2}
21)。
现在要扣除那些不合法的情况(这也正是
E
i
E_i
Ei 可能互不相同的原因),考虑枚举这一过程中经过的第一个被标记过的点,假定它是第
j
j
j 个被标记的点(
j
j
j 可以等于
i
i
i),那么一定存在一个
k
(
1
≤
k
<
m
)
k(1 \le k < m)
k(1≤k<m) 使得第
j
j
j 个硬币序列长度为
k
k
k 的后缀等于第
i
i
i 个硬币序列长度为
k
k
k 的前缀,据此可以对于每一个
i
i
i 列出一个方程:
E
i
+
∑
j
=
1
n
(
∑
k
=
1
m
−
1
[
p
r
e
(
i
,
k
)
=
s
u
f
(
j
,
k
)
]
1
2
m
−
k
)
E
j
−
1
2
m
E
0
=
0
E_i + \sum \limits_{j = 1}^{n} (\sum \limits_{k = 1}^{m - 1}[pre(i,k)=suf(j,k)] \frac{1}{2^{m - k}})E_j - \frac{1}{2^m}E_0 = 0
Ei+j=1∑n(k=1∑m−1[pre(i,k)=suf(j,k)]2m−k1)Ej−2m1E0=0
其中
p
r
e
(
i
,
k
)
pre(i, k)
pre(i,k) 即表示第
i
i
i 个硬币序列长度为
k
k
k 的前缀,
s
u
f
(
j
,
k
)
suf(j,k)
suf(j,k) 同理。
实现时可以通过哈希暴力判断前后缀相等,时间复杂度
O
(
n
3
)
O(n^3)
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]);}
AddressLOJ#2004 洛谷P3706 BZOJ4820Solution尝试把这题讲得更为清楚些。Part 1左转这道题的弱化版 BZOJ1444 [JSOI2009]有趣的游戏。考虑建出 AC 自动机,则一个长度任意的字符串对应了 AC 自动机上的一条有向路径(可经过重复点)。直观的想法是设 PiP_iPi 表示到达 AC 自动机上结点 iii 的概率,最后答案即...