[SDOI2017]硬币游戏

题目

概率生成函数牛逼!

显然我们还是先设些生成函数出来

(f_{i,j})表示第(i)个人在第(j)次抛硬币之后获胜的概率,(g_i)表示第(i)抛硬币之后无人获胜的概率,对应的生成函数分别是(F_i(x),G(x))

先明确一下我们要求的东西就是(F_i(1))

非常显然的有(g_i=g_{i+1}+sum_{j=1}^nf_{j,i+1})

对应成生成函数

[xG(x)+1=G(x)+sum_{i=1}^nF_{i}(x) ]

代入(x=1)

[sum_{i=1}^nF_i(1)=1 ]

其实这个好像非常显然并没有必要这样来搞

到现在我们其实还什么都没得到啊

考虑写一个牛逼的柿子

[G(x)(frac{1}{2}x)^m=sum_{i=1}^nsum_{j=1}^ma_{t,i,j}F_i(x)(frac{1}{2}x)^{L-j} ]

其中(a_{t,i,j})表示第(t)个串的(j)前缀是否是(i)串的一个(j)后缀,这个我们能用(hash)(O(n^2m))的时间内求出来

而且值得注意的是上面的柿子对于任意的(1leq t leq n)都是成立的

就是我们在一个还没有结束的串之后补上一个(t)串,得到了一个新的已经结束了的串,可能这个新串在补完(t)串之前就已经结束了,于是我们得把考虑把其他串结束的状态往后钦定一些字符得到这个对应的状态

我们还是代入(x=1),得到了

[G(1)=sum_{i=1}^nsum_{j=1}^ma_{t,i,j}F_i(1)2^{j} ]

我们能利用上面的柿子得到(n)个方程,之后还有一个方程(sum_{i=1}^nF_i(1)=1)这样一共(n+1)个方程解出这(n+1)个量

代码

#include<cstdio>
#define re register
#define uint unsigned long long
const int maxn=305;
int n,m;char S[maxn][maxn];
double pw[maxn],a[maxn][maxn],ans[maxn];
uint ha[maxn][maxn],base=233,pb[maxn];
inline uint sub(int t,int l,int r) {return ha[t][r]-pb[r-l+1]*ha[t][l-1];}
int main() {
	scanf("%d%d",&n,&m);
	for(re int i=1;i<=n;i++) scanf("%s",S[i]+1);
	pw[0]=1,pb[0]=1;
	for(re int i=1;i<=m;i++) pw[i]=pw[i-1]+pw[i-1],pb[i]=base*pb[i-1];
	for(re int i=1;i<=n;i++) 
		for(re int j=1;j<=m;j++)
			ha[i][j]=ha[i][j-1]*base+S[i][j];
	for(re int i=1;i<=n;i++) 
		for(re int j=1;j<=n;j++)
			for(re int k=1;k<=m;k++)
				if(sub(i,1,k)==sub(j,m-k+1,m)) a[i][j]+=pw[k];
	for(re int i=1;i<=n;i++) a[i][n+1]=-1;
	for(re int i=1;i<=n;i++) a[n+1][i]=1;a[n+1][n+2]=1;
	for(re int i=1;i<=n+1;i++) {
		for(re int j=n+2;j>=i;j--) a[i][j]/=a[i][i];
		for(re int j=i+1;j<=n+1;j++)
			for(re int k=n+2;k>=i;--k)
				a[j][k]-=a[j][i]*a[i][k];
	}
	ans[n+1]=a[n+1][n+2];
	for(re int i=n;i;--i) {
		ans[i]=a[i][n+2];
		for(re int j=i+1;j<=n+1;j++)
			ans[i]-=ans[j]*a[i][j];
	}
	for(re int i=1;i<=n;i++) printf("%.10lf
",ans[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/asuldb/p/10937667.html