@loj - [email protected] 「SDOI2017」硬币游戏

目录

  • @[email protected]
  • @[email protected]
  • @accepted [email protected]
  • @[email protected]

@[email protected]

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

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

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

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

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

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

原题链接

@[email protected]

考虑构造概率生成函数。记 \(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 [email protected]

#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]);
}

@[email protected]

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

我原本以为概率生成函数 PGF 和其他的什么 EGF, OGF 差不多,后来发现和我以前做过的生成函数题还是有点区别。
最大的区别可能是 EGF 或 OGF 重要的是函数的系数,而 PGF 可能需要求函数(或者导函数)在 x = 1 上的值。

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

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/12180335.html

时间: 2024-11-09 07:01:03

@loj - [email protected] 「SDOI2017」硬币游戏的相关文章

loj2004. 「SDOI2017」硬币游戏

2004. 「SDOI2017」硬币游戏 周末同学们非常无聊,有人提议,咱们扔硬币玩吧,谁扔的硬币正面次数多谁胜利. 大家纷纷觉得这个游戏非常符合同学们的特色,但只是扔硬币实在是太单调了. 同学们觉得要加强趣味性,所以要找一个同学扔很多很多次硬币,其他同学记录下正反面情况. 用 $ \texttt{H} $ 表示正面朝上, 用 $ \texttt{T} $ 表示反面朝上,扔很多次硬币后,会得到一个硬币序列.比如 $ \texttt{HTT} $ 表示第一次正面朝上,后两次反面朝上. 但扔到什么时

@loj - [email&#160;protected] 「CQOI2017」老 C 的方块

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 老 C 是个程序员. 作为一个懒惰的程序员,老 C 经常在电脑上玩方块游戏消磨时间.游戏被限定在一个由小方格排成的 R 行 C 列网格上,如果两个小方格有公共的边,就称它们是相邻的,而且有些相邻的小方格之间的公共边比较特殊. 特殊的公共边排列得有很强的规律.首先规定,第 1 行的前两个

@loj - [email&#160;protected]「CEOI2017」Building Bridges

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 有 n 根柱子依次排列,第 i 根柱子的高度为 hi .现可以花费 (hi - hj)^2 的代价建桥架在第 i 根柱子和第 j 根柱子之间. 所有用不到的柱子都会被拆除,第 i 根柱子被拆除的代价为 wi . 求用桥把第 1 根柱子和第 n 根柱子连接的最小代价.注意桥梁不能在端点以

@loj - [email&#160;protected]「SDOI2014」Lis

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定序列 A,序列中的每一项 Ai 有删除代价 Bi 和附加属性 Ci 请删除若干项,使得 A 的最长上升子序列长度减少至少 1,且付出的代价之和最小,并输出方案. 如果有多种方案,请输出将删去项的附加属性排序之后,字典序最小的一种. 输入格式 输入包含多组数据. 输入的第一行包含整数

@loj - [email&#160;protected] 「CTSC2016」时空旅行

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 2045 年,人类的技术突飞猛进,已经找到了进行时空旅行的方法.小 R 得到了一台时空旅行仪,他想用它调查不同时空中人类的发展状况. 根据平行时空理论,宇宙中存在着很多独立的时空,每个时空在下一个时间点还会分化出若干个不同的时空.宇宙是一个三维空间,人类使用空间直角坐标系来描述空间中的

@loj - [email&#160;protected] 「清华集训 2017」生成树计数

目录 @[email protected] @[email protected] @正文@ @补充@ @accepted [email protected] @[email protected] @[email protected] 在一个 s 个点的图中,存在 s - n 条边,使图中形成了 n 个连通块,第 i 个连通块中有 \(a_i\) 个点. 现在我们需要再连接 n - 1 条边,使该图变成一棵树.对一种连边方案,设原图中第 i 个连通块连出了 \(d_i\) 条边,那么这棵树 T 的

AC日记——「SDOI2017」序列计数 LibreOJ 2002

「SDOI2017」序列计数 思路: 矩阵快速幂: 代码: #include <bits/stdc++.h> using namespace std; #define mod 20170408 #define ll long long struct MatrixType { int n,m; ll ai[105][105]; void mem(int n_,int m_) { n=n_,m=m_; for(int i=0;i<=n;i++) for(int v=0;v<=m;v++

LibreOJ #2002. 「SDOI2017」序列计数

二次联通门 : LibreOJ #2002. 「SDOI2017」序列计数 /* LibreOJ #2002. 「SDOI2017」序列计数 线性筛 + 矩阵优化dp 先构造出全部情况的矩阵 用矩阵快速幂计算答案 再构造出全不是质数的矩阵 计算出答案 前一个答案减后一个答案即可 */ #include <cstdio> #include <iostream> #include <cstring> const int BUF = 12312312; char Buf[BU

loj#2269. 「SDOI2017」切树游戏

还是loj的机子快啊... 普通的DP不难想到,设F[i][zt]为带上根玩出zt的方案数,G[i][zt]为子树中的方案数,后面是可以用FWT优化的 主要是复习了下动态DP #include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; co