@description@

周末同学们非常无聊,有人提议,咱们扔硬币玩吧,谁扔的硬币正面次数多谁胜利。

大家纷纷觉得这个游戏非常符合同学们的特色,但只是扔硬币实在是太单调了。

同学们觉得要加强趣味性,所以要找一个同学扔很多很多次硬币,其他同学记录下正反面情况。

用 H 表示正面朝上, 用 T 表示反面朝上,扔很多次硬币后,会得到一个硬币序列。比如 HTT 表示第一次正面朝上,后两次反面朝上。

但扔到什么时候停止呢?大家提议,选出 n 个同学,每个同学猜一个长度为 m 的序列,当某一个同学猜的序列在硬币序列中出现时,就不再扔硬币了,并且这个同学胜利。为了保证只有一个同学胜利,同学们猜的 n 个序列两两不同。

很快, n 个同学猜好序列,然后进入了紧张而又刺激的扔硬币环节。你想知道,如果硬币正反面朝上的概率相同,每个同学胜利的概率是多少。

原题链接

@solution@

考虑构造概率生成函数。记 \(f_{i,j}\) 表示第 j 次抛硬币后第 i 名同学恰好获胜的概率,可以构造生成函数 \(F_i(x) = \sum_{p=0}f_{i, p}x^p\)。

再记辅助生成函数 \(g_{i}\) 表示第 i 次抛硬币后仍未结束的概率,构造生成函数 \(G(x) = \sum_{p=0}g_px^p\)。

最后需要求解的每个人的获胜概率 \(P_i = F_i(1) = \sum_{p=0}f_{i, p}\)。

有如下关系式存在:

\[G(x)x + 1 = G(x) + \sum_{i=1}^{n}F_i(x)
\]

意思是在一个未结束的序列后加一个字符,要么仍然未结束,要么某一个人获胜。

\[G(x)\times(\frac{1}{2})^m = \sum_{j=1}^{n}\sum_{k=1}^{m}[S_j[m-k...m-1] = S_i[0...k-1]]\times (\frac{1}{2})^{m - k}x^kF_j(x)
\]

意识是在一个未结束的序列后加上字符串 \(S_i\),那么一定会结束。

但有可能提前结束,如果结束时 j 胜利,则存在关系 \(S_j[m-k...m-1] = S_i[0...k-1]\),且反过来也是成立。

好像看不出来什么规律。不过注意到我们只需要求 \(F_i(1)\),所以可以直接代 \(x = 1\):

\[\sum_{i=1}^{n}F_i(1) = 1\\
\sum_{j=1}^{n}\sum_{k=1}^{m}[S_j[m-k...m-1] = S_i[0...k-1]]\times (\frac{1}{2})^{m - k}F_j(1) = G(1)\times(\frac{1}{2})^m
\]

这也是概率生成函数的一个好处。

至于剩下的,我们可以 kmp 求出满足 \(S_j[m-k...m-1] = S_i[0...k-1]\) 的所有 k。然后高斯消元即可。

以上这些全部抄自《浅谈生成函数在掷骰子问题上的应用》。

总之像这一类问题应该算是套路吧。。。也说不清楚怎么来的。

@accepted code@

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std; const int MAXN = 300; double M[MAXN + 5][MAXN + 5];
void gauss(int n, int m) {
int r = 0, c = 0;
while( r < n && c < m ) {
int mxr = r;
for(int i=r+1;i<n;i++)
if( fabs(M[i][c]) >= fabs(M[mxr][c]) )
mxr = i;
if( mxr != r ) {
for(int j=c;j<m;j++)
swap(M[r][j], M[mxr][j]);
}
if( M[r][c] ) {
double k = M[r][c];
for(int j=c;j<m;j++)
M[r][j] /= k;
for(int i=0;i<n;i++) {
if( i == r ) continue;
k = M[i][c];
for(int j=c;j<m;j++)
M[i][j] -= k*M[r][j];
}
r++;
}
c++;
}
} char s[MAXN + 5][MAXN + 5]; int n, m;
char t[2*MAXN + 5]; int f[2*MAXN + 5];
void get() {
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++) {
for(int k=0;k<m;k++)
t[k] = s[i][k];
t[m] = '#';
for(int k=0;k<m;k++)
t[k+m+1] = s[j][k];
f[0] = -1, f[1] = 0;
for(int k=2;k<=2*m+1;k++) {
f[k] = f[k-1];
while( f[k] != -1 && t[f[k]] != t[k-1] )
f[k] = f[f[k]];
f[k]++;
}
int p = f[2*m + 1];
while( p ) {
M[i][j] += pow(2, -(m-p));
p = f[p];
}
}
// M[i][n] = -pow(2, -m);
M[i][n] = -1;
}
for(int i=0;i<n;i++)
M[n][i] = 1;
M[n][n+1] = 1;
} int main() {
scanf("%d%d", &n, &m);
for(int i=0;i<n;i++) scanf("%s", s[i]);
get(), gauss(n + 1, n + 2);
for(int i=0;i<n;i++)
printf("%.10f\n", M[i][n+1]);
}

@details@

感觉莫名眼熟?一翻发现有一个 “[jsoi2009]有趣的游戏” 的题目,发现是自己记错了。。。

我原本以为概率生成函数 PGF 和其他的什么 EGF, OGF 差不多,后来发现和我以前做过的生成函数题还是有点区别。

最大的区别可能是 EGF 或 OGF 重要的是函数的系数,而 PGF 可能需要求函数(或者导函数)在 x = 1 上的值。

话说本题计算 2^(-300) 左右的数量级居然没什么精度误差。

最新文章

  1. 一次进程hang住问题分析。。。
  2. 无法用sysadmin权限的登录名登陆,sa密码忘了,管理员被锁在外面
  3. Rust: move和borrow
  4. HW5.27
  5. session和request的区别
  6. 覆盖equals的时候总要覆盖hashCode
  7. DataReader和DataSet的区别以及使用
  8. HTML5之画布的拖拽/拖放
  9. Laravel框架一:原理机制篇
  10. Promise实现多图预加载
  11. iOS框架搭建(MVC,自定义TabBar)--微博搭建为例
  12. HUD-1999-不可摸数
  13. nginx rewrite 实现URL跳转
  14. codeforces 3b之贪心算法
  15. vue2 兼容ie8
  16. python中字符串前的r什么意思
  17. Python HTTP 请求时对重定向中的 cookie 的处理
  18. Git冲突:commit your changes or stash them before you can merge.
  19. Vue 数据的双向绑定
  20. 再谈CentOS 7程序自启动

热门文章

  1. Hive-拉链表
  2. Vue的双向绑定原理
  3. 特效 css3 渐变背景框
  4. GC总结
  5. 【JUC】如何理解线程池?第四种使用线程的方式
  6. switch-case与if-else的转换
  7. js 获取当前日期时间
  8. 使用jetty作为内嵌服务器启动项目
  9. JavaScript (四) js的基本语法 - - 函数练习、arguments、函数定义、作用域、预解析
  10. 数据库之 MySQL --- 数据处理 之 表操作、CRUD(六)