【专题总结】概率与期望(持续更新)

概率问题的解决方法通常是设计状态并画出状态转移图,根据状态转移图用递推或解方程的方法解出目标状态的概率或期望。

概率问题的状态转移方程:

Pcur=(PlastToCur×Plast)

期望问题的状态转移方程:

Ecur=(Pnext×(Enext+Costnext))

当然,也有可以直接用概率或期望的公式算出来的题目。


LightOJ 1027

大意

起始状态为主人公在一个房间中,房间中有若干个门,打开每个门,通过等待一定的时间,要么重新回到房间内,要么走出房间,问走出房间消耗的时间的期望是多少。

思路

本题的状态只有两个, S1 表示正在选择进入哪个门的状态, S2 表示走出迷宫的状态。 S1 可以转移到两种状态即 S1 S2 ,根据这些关系把状态转移图画出来。下一步是根据状态图由已知状态的期望得出我们要求的状态的期望。这里只有两种状态,因此只需要列状态转移方程,解方程即可(相当于做了一次状态转移)。本题的方程可以表示为:

E1=P2×(0+ECost2)+P1×(E1+ECost1)

其中, E1 表示从 S1 S2 的期望时间(要求的量), ECost1 表示从 S1 S1 等待的期望时间, ECost2 表示从 S1 到走出房间等待的期望时间, P1 表示从 S1 转移到 S1 的概率, P2 表示从 S1 转移到 S2 的概率。

代码

#include <cstdio>
#include <algorithm>
using namespace std;

int t, c, n, a, s, cnt, g;

int main() {
    scanf("%d", &t);
    for(int c = 1; c <= t; c++) {
        printf("Case %d: ", c);
        scanf("%d", &n);
        s = cnt = 0;
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a);
            s += abs(a);
            cnt += (a > 0);
        }
        g = __gcd(s, cnt);
        if(cnt == 0) {
            puts("inf");
        }
        else {
            printf("%d/%d\n", s / g, cnt / g);
        }
    }
    return 0;
}

LightOJ 1038

大意

题给一个数 N ,初始有 D=N 。每步可以用 D 的一个约数(范围是 [1,D] )来代替 D 。问从 D=N D=1 的步数的期望值。

思路

因为本题的组数有很多,所以应该预处理出所有 d[i](1i1e5) ,然后根据输入直接输出。首先我们可以定义状态 i 表示当前的数为 i ,然后状态 i 能够转移到的状态是 i 的所有的约数 divisor(i) ,如果假设 d[i] 为状态 i 转移到状态 1 的期望步数, cnt(i) divisor(i) 的数目,则有

d[i]=jdivisor(i)(d[j]cnt(i))+1

divisor(i) 中含有 i (相当于状态图中出现了环),因此不能直接递推,要将式子变形为

d[i]=jdivisor(i)andji(d[j]cnt(i))+d[i]cnt(i)+1

然后就可以将 d[i] 提出,并且设 sum=jdivisor(i)andji(d[j]cnt(i)) ,于是有

d[i]=sum+cnt(i)cnt(i)1

由于求 divisor(i) 的复杂度是 O(log(i)) 的,所以总的复杂度是 O(nlog(n))

代码

#include <cstdio>

const int maxn = 1e5 + 5;
int t, n, cnt;
double sum, d[maxn];

int main() {
    d[1] = 0;
    for(int i = 2; i < maxn; i++) {
        cnt = 0;
        sum = 0;
        for(int j = 1; j * j <= i; j++) {
            if(i % j != 0) {
                continue;
            }
            sum += d[j];
            cnt++;
            if(j * j != i) {
                sum += d[i/j];
                cnt++;
            }
        }
        d[i] = (sum + cnt) / (cnt - 1);
    }
    scanf("%d", &t);
    for(int c = 1; c <= t; c++) {
        scanf("%d", &n);
        printf("Case %d: %.10f\n", c, d[n]);
    }
    return 0;
}

LightOJ 1265

大意

主人公参加一个类似于荒岛求生的真人秀节目,荒岛上有鹿,老虎和人类(主人公)三种生物。另外有五条规则:

  • 两虎相争两者皆死
  • 老虎会吃掉遇上的人
  • 老虎会吃掉遇上的鹿
  • 人可以决定杀不杀死遇上的鹿
  • 两只鹿相遇后什么事都不发生

题给老虎和鹿的数量,求人生存下来的概率是多少。

思路

这题当然可以定义状态 (i,j) ——当前存在着 i 只老虎和 j 只鹿,然后用动态规划解决问题。但是本题鹿的数量不影响人的生存,因此我们可以直接不管它。重新定义状态 i 为当前存在多少只老虎。当 i 为奇数的时候,人一定不能生存,因为老虎都是成对死亡的。当 i 为偶数的时候,人能生存下来的状态的转移过程如: t>t2>...i>i2>...2>0 (每次减少 2 )。当状态为 i 时,向 i2 转移的概率为 i(i1)i(i+1)=i1i+1 。于是我们就可以用迭代的方式转移状态,并根据 Pcur=PlastToCur×Plast 计算概率。(初始状态概率为1)

代码

#include <cstdio>

int t, n, m;
double ans;

int main() {
    scanf("%d", &t);
    for(int c = 1; c <= t; c++) {
        printf("Case %d: ", c);
        scanf("%d%d", &n, &m);
        if(n == 0) {
            puts("1.0000000000");
            continue;
        }
        if(n % 2 == 1) {
            puts("0.0000000000");
            continue;
        }
        ans = 1;
        while(n > 0) {
            ans *= 1.0 * (n - 1.0) / (n + 1.0);
            n -= 2;
        }
        printf("%.10f\n", ans);
    }
    return 0;
}

LightOJ 1408

大意

一个人如果连续击中 k1 次球或者连续击不中球 k2 次练习就结束了,其中击不中球的概率为 p 。求击球次数的期望值。

思路

我们令状态 1,2,3,...,i 表示连续击中 i 次球,而状态 1,2,3,...,i 表示连续击不中 i 次球。 那么状态的转移情况应该是:

0 转移到 1 (击中)和 1 (击不中)

1 转移到 2 (击中)和 1 (击不中)
2 转移到 3 1
……
k11 转移到 k1 1

1 转移到 2 (击不中)和 1 (击中)
2 转移到 3 1
……
(k21) 转移到 k2 1

由于状态的转移产生回路,因此无法用动态规划解决。但我们不难发现,我们仍然可以列出 k1+k2 条状态转移方程(不考虑 0 状态),其中有 k1+k2 个未知数,因此理论上可以将所有的未知数解出来。我们设某个状态 i 下,击球次数的期望值为 E(i) 。那么我们先尝试将 E(1) 用状态转移方程表示出来。设 q=(1p) ,有

E(1)=qE(2)+pE(1)+1

若将 E(1) 看成常量的话,则上式可以看成连接 E(i) E(i+1) 的桥梁。将上式中的 E(2) 展开又如何呢?我们有

E(1)=q2E(3)+p(q+1)E(1)+q+1

若一直展开下去,就会有

E(1)=qk11E(k1)+pi=0k12qi×E(1)+i=0k12qi

同样,对于 E(1) ,用类似的处理方法有

E(1)=qk21E(k2)+qi=0k22pi×E(1)+i=0k22pi

显然 E(1)=E(1)=0 ,另外和式也都可以展开,因此

E(1)=(1qk11)E(1)+1qk111q

E(1)=(1pk21)E(1)+1pk211p

联立两式,设 a=qk11,b=pk21 ,得

E(1)=p(1a)(1b)+q(1a)pq(a+bab)

E(1)=(1b)E(1)+1bq

进而得到答案

E(0)=qE(1)+pE(1)

代码

#include <cstdio>
#include <cmath>

const double eps = 1e-10;
int k1, k2;
double p, q;

int main() {
    int t;
    int cas = 1;
    scanf("%d",&t);
    while(t--) {  
        scanf("%lf%d%d", &p, &k1, &k2);
        if(p < eps) {
            printf("Case %d: %d\n",cas++, k1);
            continue;
        }
        if(1 - p < eps) {
            printf("Case %d: %d\n",cas++, k2);
            continue;
        }
        q = 1.0 - p;
        double s1 = 1 - pow(q, k1 - 1);
        double s2 = 1 - pow(p, k2 - 1);
        double tmp1 = s1 / p;
        double tmp2 = s2 / q;
        double g = (tmp1 * tmp2 * q +tmp2) / (1 - tmp1 * tmp2 * p * q);
        double f = tmp1 * (p * g + 1);
        printf("Case %d: %lf\n",cas++, q * f + p * g + 1);
    }
}

POJ 2096

大意

某个系统中有 s 个子系统和 n bug 类型,该系统每天会出现一个 bug (属于某个子系统和某个 bug 类型),bug的类型是等概率的,bug也是等概率地出现在每个子系统的。问所有子系统都出现 bug 且所有的 bug 类型都出现的期望天数。

思路

这个问题的时间线上有很多状态,但归根结底可以用 (i,j) 来表示这些状态。 d[i][j] 表示出现 i bug 类型和 j 个子系统出现 bug 的情况下,期望还需要多少天能够达到目标。显然, d[n][s]=0 ,因为当目标达到后就不需要额外的天数了。接下来画出状态转移图。每个状态 (i,j) 可以转移到状态 (i,j)(i+1,j)(i,j+1) (i+1,j+1) ,因为当前状态的下一天出现的 bug 可能是任何类型也可能属于任何子系统。然后就可以列出状态转移方程,本题的方程是:

d[i][j]=ijnsd[i][j]+(ni)jnsd[i+1][j]+i(sj)nsd[i][j+1]+(ni)(sj)nsd[i+1][j+1]+1

最后根据方程从 d[n][s] 向前递推,最后 d[0][0] 就是答案。

代码

#include <cstdio>

const int maxn = 1010;
int n, s; 
double d[maxn][maxn];

int main() {
    scanf("%d%d", &n, &s);
    for(int i = n; i >= 0; i--) {
        for(int j = s; j >= 0; j--) {
            if(i == n && j == s) {
                continue;
            }
            d[i][j] = 1.0 * n * s;
            d[i][j] += 1.0 * (n - i) * j  * d[i+1][j];
            d[i][j] += 1.0 * i  * (s - j) * d[i][j+1];
            d[i][j] += 1.0 * (n - i) * (s - j) * d[i+1][j+1];
            d[i][j] /= (1.0 * n * s - i * j);
        }
    }
    printf("%.4f\n", d[0][0]);
    return 0;
}

Codeforces 540D

大意

某个小岛上有 r 个石头, p 个布和 s 个剪刀。任意两个个体的相遇都是等概率的。问小岛上最后只剩下剩下石头,剪刀或布的概率分别是多少。

思路

我们可以用 (i,j,k) 表示当前状态——岛上还剩下 i 个石头, j 个剪刀和 k 个布。那么 d[i][j][k] 当仁不让地被我们用来表示状态 (i,j,k) 发生的概率。显然, d[r][s][p]=1 ,因为这是初始状态,是 100 会出现的状态。另外,状态 (i,j,k) 在时间线上的下一个状态有可能是 (i1,j,k)(i,j1,k) (i,j,k1) 并且转移到这些状态的概率也能够被求出。致此,我们可以列出状态转移方程了:

d[i][j][k]=PrMeets(i,j)×d[i][j1][k]+PsMeetp×d[i][j][k1]+PrMeetp×d[i1][j][k]

最后剩下两种生物的时候就能得到结果。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 105;
int r, s, p, m;
double sum, a, b, c, d[maxn][maxn][maxn];

int main() {
    scanf("%d%d%d", &r, &s, &p);
    memset(d, 0, sizeof(d));
    d[r][s][p] = 1;
    for(int i = r; i > 0; i--) {
        for(int j = s; j > 0; j--) {
            for(int k = p; k > 0; k--) {
                sum = i * j + j * k  + k * i;
                d[i][j-1][k] += i * j / sum * d[i][j][k];
                d[i][j][k-1] += j * k / sum * d[i][j][k];
                d[i-1][j][k] += i * k / sum * d[i][j][k]; 
            }
        }
    }
    a = b = c = 0;
    m = max(max(r, s), p);
    for(int i = 1; i <= m; i++) {
        for(int j = 1; j <= m; j++) {
            a += d[i][j][0];
            b += d[0][i][j];
            c += d[i][0][j];
        }
    }
    printf("%.10f %.10f %.10f\n", a, b, c);
    return 0;
}

Codeforces 697D

思路

如果想以树的节点编号做状态来做树形动态规划的话,对于每个节点,都要枚举子节点的访问顺序。这样的复杂度太高了。所以我们转而用公式法或者贡献度法来解决。对于一个点 v strarttime[v] 显然等于先于 v 访问的节点的个数加上 1 。那么每个点 u 对点 starttime[v] 期望的贡献度就是 u 先于 v 访问的概率 pu 。那么 starttime[v] 的期望为 pu+1 。下面就考察每个点先于 v 访问的概率。首先对于 v 的祖先 u1pu1=1 ,其次对于 v 的子树上的节点 u2pu2=0 。最后对于其它所有节点 u3pu3=0.5 (因为在 v u3 的最近公共祖先的时候,往这两个节点所在的子树走的概率是相等的)。现在问题就转化成如何计算 u3 的数量。设 v 在树中的深度为 d[v] (根节点深度为 0 ), v 的子树的节点个数为 c[v] ,那么 u3 的数量就是 nc[v]d[v] 。因此答案就是 nc[v]d[v]2+d[v]+1

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 10;
int n, p, ans, c[maxn], d[maxn];
vector <int> G[maxn];

void dfs(int u) {
    c[u] = 1;
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        d[v] = d[u] + 1;
        dfs(v);
        c[u] += c[v];
    }
}

int main() {
    scanf("%d", &n);
    for(int i = 2; i <= n; i++) {
        scanf("%d", &p);
        G[p].push_back(i);
    }
    dfs(1);
    for(int i = 1; i <= n; i++) {
        ans = n - c[i] + d[i] + 2;
        printf("%d.", ans / 2);
        printf(ans & 1 ? "5 " : "0 ");
    }
    puts("");
    return 0;
}

Codeforces 697E

思路

因为这是一个概率问题,所以先按照状态转移的思想来思考。如果将初始状态定义为 (1,2,3) ,那么状态空间将只有 6 个元素—— (1,2,3)(1,3,2)(2,1,3)(2,3,1)(3,1,2)(3,2,1) 。但是将状态转移图画好后又会发现没有什么我们需要的线索。那么换一种状态定义方式呢?假设状态 i i 次交换后的状态,令 d[i] i 次交换后初始状态下的中间的杯子(我们可以暂时称之为第 2 个杯子)依然在中间的概率。我们试图寻找 d[i] d[i1] 的关系。不难发现,当状态为 i1 时第 2 个杯子在中间的话,到了状态为 i 的时候第 2 个杯子就不可能在中间了。于是有如下关系

d[i]=1d[i1]2

因为要得到最最简分数形式的概率,因此光得到递推公式是不够的,我们要求一个能直接计算出结果的封闭形式。于是用待定系数法可以得到一个等比数列的递推公式

d[i]13=12(d[i1]13)

解这个递推公式得

d[n]=(1)n+2n13×2n1

凑巧的是, (1)n+2n1 正好是 3 的倍数(与 2 的幂的相邻的两个数中一定有一个是 3 的倍数)而且(1)n+2n1除以 3 一定能得到奇数,这样就能与上式分母的偶数互质了。所以有

p=(1)n+2n13

q=2n1

题目就得解了。另外要注意的是,对分数取 mod 的时候要对分母计算模逆元,也就是要算出 3 的模逆元。计算 2n1 的时候可以用快速幂算法来算。

最后,偶然发现像P这样的数被称为 Jacobsthalnumbers 维基百科)。

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll mod = 1e9 + 7;
bool one = true;
int k, odd = 1;
ll a, p, q = 2;

// 快速幂算法
ll modPow(ll a, ll n, ll mod) {
    ll ans = 1;
    for(; n > 0; n >>= 1) {
        if(n & 1) {
            ans = (ans * a) % mod;
        }
        a = (a * a) % mod;
    }
    return ans;
}

// 求模逆元
ll modInv(ll a, ll mod) {
    return modPow(a, mod - 2, mod);
}

int main() {
    scanf("%d", &k);
    for(int i = 0; i < k; i++) {
        scanf("%I64d", &a);
        odd &= (a & 1);
        q = modPow(q, a, mod);
        if(a > 1) {
            one = false;
        }
    }
    q = (q * modInv(2, mod)) % mod;
    if(one == true) {
        p = 0;
    }
    else {
        p = q;
        p += (odd ? -1 : 1);
        p = (p * modInv(3, mod)) % mod;
    }
    printf("%I64d/%I64d\n", p, q);
    return 0;
}

HDU 5753

大意

题给一个序列 c ,另规定 1n 的排列为 h 。定义 f(h)=ni=1ci[hi>hi1andhi>hi+1] 。求 f(h) 的期望值。

思路

根据本题的数据规模来看,肯定无法枚举排列然后算期望了。也不好设计出状态来通过状态转移方程解决问题。一个可行的思维是算出序列 c 中每个元素对期望的贡献。

  • 当考虑 c 的第 1 个元素 c1 时,我们只要考虑前两个元素的排列,当前两个元素呈现出 c1>c2 c1 的贡献就会被算进期望中。显然 c1>c2 的概率是 12

    • 当考虑 c 的第 2 个元素 c2 时,我们只要考虑前三个元素的排列,当前三个排列呈现出 c1<c2,c3<c2 时, c2 的贡献就回被算进期望中。显然这种排列的概率是 13
    • 3 到第 n1 个元素的贡献与第 2 个元素的贡献相似。第 n 个元素的贡献与第 1 <script id="MathJax-Element-229" type="math/tex">1</script> 个元素的贡献相似。
    • 于是我们枚举每个元素,将其贡献加入期望中即可。

      代码

      #include <bits/stdc++.h>
      using namespace std;
      
      int n, a;
      double ans;
      
      int main() {
          while(~scanf("%d", &n)) {
              ans = 0;
              for(int i = 1; i <= n; i++) {
                  scanf("%d", &a);
                  if(i == 1 || i == n) {
                      ans += 1.0 * a / 2;
                  }
                  else {
                      ans += 1.0 * a / 3;
                  }
              }
              if(n == 1) {
                  printf("%.5f\n", 1.0 * a);
              }
              else {
                  printf("%.5f\n", ans);
              }
          }
          return 0;
      }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值