[SDOI2017] 硬币游戏
一、题目
二、解法
一定要先完全搞懂 歌唱王国
那个题再来做这道题,一模一样的思路。
设 \(F_i(x)\) 表示 \(A_i\)(就表示那个硬币序列)在某个时刻出现的概率生成函数,\(G(x)\) 表示某个时刻还未结束的概率生成函数,现在就来找关系列方程吧!
有一个方程是最难列的,但是思路是固定的。我们考虑对于 \(A_i\) 直接在 \(G(x)\) 的基础上强行加一个长度为 \(m\) 的字符串构成 \(A_i\),但是可能会有其他的 \(A_j\) 中途就已经被构成了,所以可能没有加到 \(m\) 个字符就停止了!
但是这时候 \(A_i\) 的一个前缀一定等于 \(A_j\) 的一个后缀,我们枚举这个公共部分的长度,设 \(a_{i,j,k}\) 表示 \(A_i[1,k]=A_j[m-k+1,m]\),就可以结合 \(F_j(x)\) 列出这样的方程:
\[G(x)(\frac{x}{2})^m=\sum_{j=1}^n\sum_{k=1}^m a_{i,j,k}F_j(x)(\frac{x}{2})^{m-k}
\]
把 \(x=1\) 带入,因为我们只需要求 \(F_i(1)\),系数和也是相等的:
\[G(1)=\sum_{j=1}^n\sum_{k=1}^ma_{i,j,k}F_j(1)2^k
\]
这里已经有 \(n+1\) 个未知数和 \(n\) 个方程了,还有一个最简单的方程:
\[\sum_{i=1}^n F_i(1)=1
\]
直接高斯消元即可,时间复杂度 \(O(n^3)\),震惊,\(double\) 竟然可以存 \(2^{30}\)
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 305;
#define db double
#define eps 1e-9
#define ull unsigned long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m;char s[M];ull h[M][M],pw[M];db a[M][M],b[M];
ull Hash(int x,int l,int r)
{
return h[x][r]-pw[r-l+1]*h[x][l-1];
}
db Abs(db x)
{
return x>0?x:-x;
}
void guass(int n)
{
for(int i=1;i<=n;i++)
{
int mx=i;
for(int j=i+1;j<=n;j++)
if(Abs(a[mx][i])<Abs(a[j][i]))
mx=j;
if(i!=mx) swap(a[i],a[mx]);
for(int j=1;j<=n;j++)
{
if(i==j) continue;
db t=a[j][i]/a[i][i];
for(int k=i;k<=n+1;k++)
a[j][k]-=a[i][k]*t;
}
}
}
signed main()
{
n=read();m=read();pw[0]=b[0]=1;
for(int i=1;i<=m;i++)
{
pw[i]=pw[i-1]*2;
b[i]=b[i-1]*2;
}
for(int i=1;i<=n;i++)
{
scanf("%s",s+1);
for(int j=1;j<=m;j++)
h[i][j]=(s[j]=='H')+h[i][j-1]*2;
}
for(int i=1;i<=n;i++)//列第i个方程
{
for(int j=1;j<=n;j++)
for(int k=1;k<=m;k++)
if(Hash(i,1,k)==Hash(j,m-k+1,m))
a[i][j]+=b[k];
a[i][n+1]=-1;
}
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++)
printf("%.10f\n",a[i][n+2]/a[i][i]);
}