把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ4820】[SDOI2017] 硬币游戏(高斯消元)

点此看题面

  • \(n\)个人,每个人有一个长度为\(m\)的仅由HT组成的字符串。
  • 现往一个初始为空的字符串末尾加字符,每次等概率加入HT,对应字符串最早出现的人获胜。
  • 求每个人获胜的概率。
  • \(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;
}

posted @ 2021-03-25 13:56  TheLostWeak  阅读(32)  评论(0编辑  收藏  举报