【BZOJ4820】[SDOI2017] 硬币游戏(高斯消元)
- 有\(n\)个人,每个人有一个长度为\(m\)的仅由
H
和T
组成的字符串。 - 现往一个初始为空的字符串末尾加字符,每次等概率加入
H
和T
,对应字符串最早出现的人获胜。 - 求每个人获胜的概率。
- \(n,m\le300\)
一个没啥启发性的暴力
可参见这道题:【BZOJ1444】[JSOI2009] 有趣的游戏。
就是一个很直接的建\(AC\)自动机+高斯消元的过程。
当然由于本题的数据范围是\(n,m\le300\),这种做法是过不了的,而且对于此题的正解也没啥启发性。(除了高斯消元?)
神奇的正解
我们设\(S\)表示不合法状态,设\(T_i\)表示第\(i\)个人获胜的状态。
考虑在一个不合法状态之后加上\(s_i\),它必然能够结束,但不一定会等到\(s_i\)完全加完才结束。
实际上,对于任意一个\(T_j\),只要满足\(s_i\)长度为\(k\)的前缀和\(s_j\)长度为\(k\)的后缀相同,\(S+s_i[1...m]\)就有可能可以表示为\(T_j+s_i[k+1...m]\)。
从\(S\)到\(S+s_i[1...m]\)相当于要加上\(m\)个特定的字符,概率是\(\frac1{2^m}\);同理从\(T_j\)到\(T_j+s_i[k+1...m]\)相当于要加上\(m-k\)个特定字符,概率是\(\frac1{2^{m-k}}\)。
所以说,我们根据每个\(s_i\)都可以列出一个方程:
\[\frac1{2^m}S=\sum_{j=1}^n\sum_{k=1}^m[s_i[1...k]=s_j[m-k+1...m]]\frac1{2^{m-k}}T_j
\]
这样有\(n+1\)个元,\(n\)个方程,似乎还缺了什么。
于是我们考虑到每局游戏总恰有一个人赢,也就是说实际上还有一个方程:
\[\sum_{i=1}^nT_i=1
\]
加上这个方程就可以高斯消元了。
代码:\(O(n^3)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 300
#define DB double
using namespace std;
int n,m;DB pp[N+5];char s[N+5][N+5];
struct Hash
{
#define ull unsigned long long
#define CU Con ull&
ull x,y;I Hash() {x=y=0;}I Hash(CU a) {x=y=a;}I Hash(CU a,CU b):x(a),y(b){}
I Hash operator + (Con Hash& o) Con {return Hash(x+o.x,y+o.y);}
I Hash operator - (Con Hash& o) Con {return Hash(x-o.x,y-o.y);}
I Hash operator * (Con Hash& o) Con {return Hash(x*o.x,y*o.y);}
I bool operator == (Con Hash& o) Con {return x==o.x&&y==o.y;}
}seed(302627441,11743207),pre[N+5][N+5],suf[N+5][N+5],pw[N+5];
DB a[N+5][N+5],v[N+5];I void Gauss(CI n)//高斯消元模板
{
RI i,j,k;DB t;for(i=1;i<=n;++i)
{
for(j=i,k=i+1;k<=n;++k) fabs(a[k][i])>fabs(a[j][i])&&(j=k);for(k=i;k<=n;++k) swap(a[i][k],a[j][k]);
for(j=i+1;j<=n;++j) for(v[j]+=v[i]*(t=-a[j][i]/a[i][i]),k=i;k<=n;++k) a[j][k]+=a[i][k]*t;
}
for(i=n;i;--i) for(v[i]/=a[i][i],j=i-1;j;--j) v[j]-=a[j][i]*v[i];
}
int main()
{
RI i,j;for(scanf("%d%d",&n,&m),pw[0]=pp[0]=i=1;i<=m;++i) pw[i]=pw[i-1]*seed,pp[i]=pp[i-1]/2;
for(i=1;i<=n;++i) scanf("%s",s[i]+1);
for(i=1;i<=n;++i) for(j=1;j<=m;++j) pre[i][j]=pre[i][j-1]*seed+s[i][j];//前缀哈希
for(i=1;i<=n;++i) for(j=m;j>=1;--j) suf[i][j]=suf[i][j+1]+pw[m-j]*s[i][j];//后缀哈希
RI k;for(i=1;i<=n;++i) for(j=1;j<=n;++j) for(k=1;k<=m;++k) pre[i][k]==suf[j][m-k+1]&&(a[i][j]+=pp[m-k]);//根据每个串列出方程
for(i=1;i<=n;++i) a[i][n+1]=-pp[m],a[n+1][i]=1;v[n+1]=1;//加上每个方程中S的项;列出第n+1个方程∑T=1
for(Gauss(n+1),i=1;i<=n;++i) printf("%.10lf\n",v[i]);return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒