【SDOI2017】硬币游戏(高斯消元)(AC自动机)

传送门


因为不取模,所以高消的时候可能会在一些奇奇怪怪的地方炸精度。

都2017的省选了,在这种题目上为什么不取模啊

对计数和概率取模这种套路不是在2014年的时候就在各种OJ的比赛里面普及过了吗。。。

这道题的严格推导要用到无限集合的理论,我自己尝试证明了一遍,有点复杂,而且对于不懂高等概率的人来说可能看不懂,这里只给一个口胡式的解答。

题解:

首先我们明白一点,在接下来的 t t t次随机中强行指定一个长度为 t t t的串出现的概率为 2 t 2^t 2t,这里的出现是指接下来 t t t次立即出现。

设答案为 p i , i = 1 , 2 ⋯ n p_i,i=1,2\cdots n pi,i=1,2n

假设在进行了足够多次随机之后(中间就算出现了某一个串也不停下)我们强行停止这个过程(就算一个串都没有出现也停止),设 x i ( i = 1 , 2 , ⋯ n ) x_i(i=1,2,\cdots n) xi(i=1,2,n)表示在该串中第一个出现的串为 i i i的概率,一个串都没有出现的概率为 x 0 x_0 x0

注意,在进行了足够多次乃至无限次随机之后,有 x 1 : x 2 : ⋯ : x n = p 1 : p 2 : ⋯ : p n x_1:x_2:\cdots :x_n=p_1:p_2:\cdots :p_n x1:x2::xn=p1:p2::pn。没好好学过微积分请感性理解,学过微积分请用极限理解。

所以我们求出 x i = k x 0 x_i=kx_0 xi=kx0之后利用 ∑ i = 1 n x i = 1 \sum_{i=1}^nx_i=1 i=1nxi=1来解方程就行了。

现在请忘掉上面那个关于足够多次的构造。

N N N表示还没有结束的局面集合,我们考虑在 N N N后面加一个 s i s_i si来让游戏强行结束。

但是注意,这时候获胜的不一定是 s i s_i si。。

如果 s i s_i si的前缀和另一个串 s j s_j sj的后缀相同,那么就可能会出事。。。

出事的概率是所有 i i i的前缀等于 j j j的后缀的长度的 1 2 \frac{1}{2} 21的幂次之和。

对这个玩意列方程求解即可。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cerr;
using std::cout;

cs int N=3e2+7,M=1e5+7;
cs double eps=1e-10,P=.5;

int n,m;
int son[M][2],cnt,fail[M],d[M],ps[M];
std::vector<int> nd[M];
double p[M],ans[M],A[N][N];

inline void ins(int id){
	static char s[N];
	scanf("%s",s+1);
	int cur=0;
	for(int re i=1;i<=m;++i){
		int c=s[i]=='H';
		if(!son[cur][c]){son[cur][c]=++cnt;d[cnt]=d[cur]+1;}
		nd[cur=son[cur][c]].push_back(id);
	}ps[id]=cur;
}

inline void get_fail(){
	std::queue<int> q;
	if(son[0][0])q.push(son[0][0]);
	if(son[0][1])q.push(son[0][1]);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int re i=0;i<2;++i)
		if(son[u][i])fail[son[u][i]]=son[fail[u]][i],q.push(son[u][i]);
		else son[u][i]=son[fail[u]][i];
	}
}

inline void get(int x){
	for(int re u=ps[x];u;u=fail[u])
	for(int re v:nd[u])A[v][x]+=p[m-d[u]];
}

inline void Gauss(int n){
	for(int re i=1;i<=n;++i){
		int p;for(p=i;p<=n;++p)if(fabs(A[p][i])>eps)break;
		if(p>n)p=i;
		if(p!=i)for(int re j=i;j<=n+1;++j)std::swap(A[p][j],A[i][j]);
		double t=A[i][i];
		for(int re j=i;j<=n+1;++j)A[i][j]/=t;
		for(int re j=i+1;j<=n;++j)if(fabs(A[j][i])>eps){
			double t=A[j][i];
			for(int re k=i;k<=n+1;++k)A[j][k]-=t*A[i][k];
		}
	}
	for(int re i=n;i;--i){
		ans[i]=A[i][n+1];
		for(int re j=i+1;j<=n;++j)ans[i]-=A[i][j]*ans[j];
	}
}

signed main(){
#ifdef zxyoi
	freopen("coin.in","r",stdin);
#endif
	scanf("%d%d",&n,&m);
	p[0]=1;for(int re i=1;i<=m;++i)p[i]=p[i-1]*P;
	for(int re i=1;i<=n;++i)ins(i);get_fail();
	for(int re i=1;i<=n;++i)get(i);
	for(int re i=1;i<=n;++i)A[i][n+1]=-p[m],A[n+1][i]=1;
	A[n+1][n+2]=1;Gauss(n+1);
	for(int re i=1;i<=n;++i)printf("%.10f\n",ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值