P3706 [SDOI2017]硬币游戏 题解

Description

n n n 个人,第 i i i 个人给出一个长度为 m m m 的仅包含 AB 的字符串 s i s_i si

扔若干次硬币,并记录下硬币落地时朝上的正反面情况。当一个人猜的序列在硬币序列中出现时,此人胜利,且扔硬币活动停止。

你需要对于每个 i i i,求出第 i i i 个人获胜的概率。

1 ≤ n , m ≤ 300 1 \le n,m \le 300 1n,m300 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=1m[prei,k=sufj,k]2mk1P(j)

这个式子看起来很奇怪,下面详细分析。

首先,我们需要牢记, P ( j ) P(j) P(j) 就是等概率随机构造出一个任意长的二进制串,使得 s j s_j sj 在其中第一个出现的概率;乘上这个值,保证了 j j j 的胜利。后面的 1 2 m − k \frac {1} {2^{m-k}} 2mk1,指的是在现有的 s 0 ′ s'_0 s0(即 s 0 s_0 s0 后加上了 s u f j , k suf_{j,k} sufj,k 的新串)后,加上 m − k m-k mk 个位置得到 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=1nk=1m[prei,k=sufj,k]2mk1P(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=1nP(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;
}

猜你喜欢

转载自blog.csdn.net/Cherrt/article/details/118792202