Description
有
n
n
n 个人,第
i
i
i 个人给出一个长度为
m
m
m 的仅包含 A
和 B
的字符串
s
i
s_i
si。
扔若干次硬币,并记录下硬币落地时朝上的正反面情况。当一个人猜的序列在硬币序列中出现时,此人胜利,且扔硬币活动停止。
你需要对于每个 i i i,求出第 i i i 个人获胜的概率。
1 ≤ n , m ≤ 300 1 \le n,m \le 300 1≤n,m≤300, s s s 中的元素两两不同。
Solution
为方便叙述,做出如下约定:
-
令字符串 T T T 为 i i i 获胜的终止串,当且仅当 s i s_i si 在 T T T 中出现的位置前于任意其他 s j ( i ≠ j ) s_j(i \neq j) sj(i=j) 在 T T T 中出现的位置。
-
令字符串 T T T 为非终止串,当且仅当不存在 s i s_i si 是 T T T 的子串。
若想要让第 i i i 个人获胜,那么终止串必定会是一个非终止串 s 0 s_0 s0 加 s i s_i si。
但是,随意挑选一个
s
0
s_0
s0 并在后面拼上
s
i
s_i
si,可能并不会让
i
i
i 获胜。例如,当
s
0
=
s_0=
s0=bb
时,
s
1
=
s_1=
s1=ba
,
s
2
=
s_2=
s2=ab
时,虽然 ba
在
s
0
+
s
1
=
s_0+s_1=
s0+s1=bbba
中出现,但
s
2
s_2
s2 出现在更加靠前的位置。
何时会出现这种情况呢?不难发现,若 p r e i , k = s u f j , k pre_{i,k}=suf_{j,k} prei,k=sufj,k,就有一定概率出现上述情况。其中, p r e i , k pre_{i,k} prei,k 表示 s i s_i si 的长度为 k k k 的前缀, s u f j , k suf_{j,k} sufj,k 表示 s j s_j sj 的长度为 k k k 的后缀。
令产生非终止串
s
0
s_0
s0 的概率为
X
X
X,则产生
s
0
+
s
i
s_0+s_i
s0+si 的概率为
X
2
m
\frac {X} {2^m}
2mX。
注意到,在逐位添加的过程中,最终总会有一个人取得胜利;换言之,我们可以将 X 2 m \frac {X} {2^m} 2mX 用另一种方式表达出来。即,我们枚举 j j j,计算出此时 j j j 获胜的概率,这些贡献之和就是 X 2 m \frac {X} {2^m} 2mX。贡献是
∑ k = 1 m [ p r e i , k = s u f j , k ] 1 2 m − k P ( j ) \sum_{k=1}^m [pre_{i,k}=suf_{j,k}] \frac {1} {2^{m-k}} P(j) k=1∑m[prei,k=sufj,k]2m−k1P(j)
这个式子看起来很奇怪,下面详细分析。
首先,我们需要牢记, P ( j ) P(j) P(j) 就是等概率随机构造出一个任意长的二进制串,使得 s j s_j sj 在其中第一个出现的概率;乘上这个值,保证了 j j j 的胜利。后面的 1 2 m − k \frac {1} {2^{m-k}} 2m−k1,指的是在现有的 s 0 ′ s'_0 s0′(即 s 0 s_0 s0 后加上了 s u f j , k suf_{j,k} sufj,k 的新串)后,加上 m − k m-k m−k 个位置得到 s 0 + s i s_0+s_i s0+si 的概率。
这时你可能会有一个疑问—— j j j 已经获胜了,为什么还要继续添加下去呢?原因是—— X 2 m \frac {X} {2^m} 2mX 直接计算了 s 0 + s i s_0+s_i s0+si 出现的概率,并没有考虑因在添加过程中突然有人获胜导致赌博终止的情况。此时,我们计算 j j j 的贡献时,必须将 s 0 + s i s_0+s_i s0+si 补充完整以保证等式的成立。
从而我们列出了 n n n 个方程,第 i i i 个方程形如:
X 2 m = ∑ j = 1 n ∑ k = 1 m [ p r e i , k = s u f j , k ] 1 2 m − k P ( j ) \frac {X} {2^m}=\sum_{j=1}^n \sum_{k=1}^m [pre_{i,k}=suf_{j,k}] \frac {1} {2^{m-k}} P(j) 2mX=j=1∑nk=1∑m[prei,k=sufj,k]2m−k1P(j)
仔细观察,发现未知数有 X X X 以及 P ( 1 ) , P ( 2 ) , P ( 3 ) , ⋯ , P ( n ) P(1),P(2),P(3),\cdots,P(n) P(1),P(2),P(3),⋯,P(n),共有 n + 1 n+1 n+1 个。 n n n 个方程不足以将这些值分别解出,所以我们还要添加一个方程。
由于最终必然有一个赢家,所以有
∑ i = 1 n P ( i ) = 1 \sum_{i=1}^n P(i)=1 i=1∑nP(i)=1
采用高斯消元解方程,采用字符串哈希判断是否满足 p r e i , k = s u f j , k pre_{i,k}=suf_{j,k} prei,k=sufj,k 即可。时间复杂度 O ( n 3 ) O(n^3) O(n3),本题被解决。
#include <bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
const int base=13331,mod=998244353,maxl=305;
const double eps=1e-9;
int read(){
int s=0,w=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') w=-w;ch=getchar();}
while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
return s*w;
}
int n,m,pre[maxl][maxl],suf[maxl][maxl],p[maxl];
double a[maxl][maxl],bin[maxl],ans[maxl];
string s[maxl];
int trans(char x){return (x=='H')?1:0;}
void init(){
bin[0]=1;
for (int i=1;i<=m;i++) bin[i]=(bin[i-1]/2.00);
p[0]=1;
for (int i=1;i<=m;i++) p[i]=(p[i-1]*base)%mod;
}
void Guass(int s){
for (int i=1;i<=s;i++){
int pos=i;
for (int j=i;j<=s;j++){
if (abs(a[j][i])>abs(a[pos][i])) pos=j;
}
swap(a[i],a[pos]);
for (int j=i+1;j<=s;j++){
double val=a[j][i]/a[i][i];
for (int k=i;k<=s+1;k++) a[j][k]-=a[i][k]*val;
}
}
ans[s]=a[s][s+1]/a[s][s];
for (int i=s-1;i>=1;i--){
for (int j=s;j>i;j--) a[i][s+1]-=a[i][j]*ans[j];
ans[i]=a[i][s+1]/a[i][i];
}
}
signed main(){
n=read(),m=read();init();
for (int i=1;i<=n;i++){
cin>>s[i];
int len=s[i].size();
for (int j=0;j<len;j++)
pre[i][j+1]=(pre[i][j]*base+trans(s[i][j]))%mod;
for (int j=len;j>=1;j--)
suf[i][j]=(suf[i][j+1]+trans(s[i][j-1])*p[len-j])%mod;
for (int j=1;j<=len/2;j++) swap(suf[i][j],suf[i][len+1-j]);
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
for (int k=1;k<=m;k++){
if (pre[i][k]==suf[j][k]) a[i][j]+=bin[m-k];
}
}
a[i][n+1]=-bin[m],a[i][n+2]=0;
}
for (int i=1;i<=n;i++) a[n+1][i]=1;
a[n+1][n+2]=1;
Guass(n+1);
for (int i=1;i<=n;i++) cout<<fixed<<setprecision(10)<<ans[i]<<endl;
return 0;
}