《算法竞赛·快冲300题》每日一题:“黑白配”

算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge。
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。

文章目录

  • 题目描述
  • 题解
  • C++代码
  • Java代码
  • Python代码

黑白配” ,链接: http://oj.ecustacm.cn/problem.php?id=1828

题目描述

【题目描述】 黑白配是一个经典的游戏,在每一轮中,孩子们将手朝上(白色)或者朝下(黑色)。
如果所有的孩子做出相同的选择,只有一个例外,那么那个例外的孩子将会被淘汰。
游戏重复进行,直到只剩下两个孩子停止。
每个孩子有一个固定的概率独立选择是否将手朝上。
给定n个孩子的概率,请输出游戏期望回合数是多少。。
【输入格式】 第一行为正整数n,n不超过20,表示孩子数量。
接下来n行,每行一个数字pi表示孩子i的概率,0.1≤pi≤0.9。。
【输出格式】 输出一个数字表示期望回合数。
注意:输出结果与标准结果的绝对误差或者相对误差小于10^-6即视为正确。。
【输入样例】

样例13
0.5
0.5
0.5

样例25
0.1
0.3
0.5
0.7
0.9

【输出样例】

样例11.3333333

样例27.4752846

题解

   首先手动计算题目的概率和期望,目的是了解题目的要求。这一题的计算不难。
   第一个样例,3人,每人出白或出黑都是0.5。第一个回合有8种情况,其中2种是全黑或全白,6种是淘汰1人剩2人。8种情况中剩下2人的概率是6×0.5×0.5×0.5 = 0.75,结束的回合数期望值是1/0.75=1.3333333。
   第二个样例,5人参加。第一轮以一定概率淘汰1人剩4人;第二轮以一定概率淘汰1人剩3人;第3论以一定概率淘汰1人剩下2人,结束。把所有情况的概率加起来,然后计算期望值。
   例如可能的情况有:
   (1)第一个回合淘汰第1人、第二个回合淘汰第2人、第三个回合淘汰第3人,剩下2人,结束。
   第一个回合淘汰第1人概率 = 第1人白且其他人全黑+第1人黑且其他人全白 = 0.1×(1-0.3)×(1-0.5)×(1-0.7)×(1-0.9) + (1-0.1)×0.3×0.5×0.7×0.9 = 0.0861;
   第二个回合淘汰第2人的概率 = 第2人白且其他人全黑+第2人黑且其他人全白 = 0.3×(1-0.5)×(1-0.7)×(1-0.9) + (1-0.3)×0.5×0.7×0.9 =0.225
   第三个回合淘汰第3人的概率 = 第3人白且其他人全黑+第3人黑且其他人全白 = 0.5×(1-0.7)×(1-0.9) + (1-0.5)×0.7×0.9 =0.33
   总概率是0.0861×0.225 = 0.006392925。
   (2)第一个回合淘汰第1人,第二个回合淘汰第3人,第二个回合淘汰第2人…
   等等,把所有情况的概率相加得到总概率p,1/p是回合数的期望值。
   以上步骤需要计算所有可能的情况的概率。并且每次淘汰一人时,只需考虑相关两个回合的概率。例如n个人淘汰1人的概率等于:淘汰这n个人中第1人的概率 × n中不包括第1人的概率 + 淘汰第2人的概率× n中不包括第2人的概率 + … + 淘汰第n人的概率× n中不包括第n人的概率。
   这显然符合DP的“重叠子问题、最优子结构”的思想。
   如何编程?有两个核心技巧。
   (1)用二进制来表示人,编程非常简洁。例如3个人,用000 ~ 111这8个数字表示游戏的组合情况:000是全朝上;111是全朝下;001是前2人朝上、第3人朝下;010是第1和第3人朝上,第2人朝下;…等等。
   (2)用DP计算。这种题是DP的套路题,称为“概率DP”,编程简洁易懂。下面是状态定义和状态转移。
   定义状态dp:dp[i]表示人数状态为i时的期望轮数。例如:11111是还有5人时的期望轮数;10110是还有第1、3、4人时的期望轮数;等等。
   状态转移,见代码中的注释。。
  计算复杂度 O ( 2 n ) O(2^n) O(2n)
【重点】 概率DP。

C++代码

#include
using namespace std;
double p[20];
double dp[(1 << 20) + 10];    //dp[i]表示剩余人数状态为i时的期望轮数
int bin_count(int x){         //返回x中有几个1,例如x=1011,返回3
    int ans = 0;
    while(x)
        ++ans, x -= (x & (-x));
    //x & (-x)是lowbit,即x的最后一个1,例如x=110,x & (-x)=2
    //x -= (x & (-x))的功能是去掉x的最后一个1
    return ans;
}
int main(){
    int n; cin >> n;
    for(int i = 0; i < n; i++)       cin >> p[i];
    for(int x = 0; x < (1 << n); x++){   //第x种情况,例如x=10110表示还有第1人、第3人、第4人
        if(bin_count(x) <= 2)  continue; //少于等于2人,结束        
        double aw = 1.0, ab = 1.0;       //当前情况下所有人出白的概率,出黑的概率
        for(int i = 0; i < n; i++)
            if(x & (1 << i))             //统计剩下的人
                aw *= p[i], ab *= (1 - p[i]); //aw:所有人朝上的概率,ab:所有人朝下的概率        
        double tot = 0.0;   //tot表示当前情况x转移到其他情况的概率(有人淘汰的概率)
        for(int i = 0; i < n; i++)
            if(x & (1 << i)){
                 //pi表示这x人中淘汰第i个人的概率
                double pi = aw / p[i] * (1 - p[i]) + ab / (1 - p[i]) * p[i];
                 //pi = 除了i以外的人都朝上的概率*i朝下的概率 + 除了i都朝下的概率*i朝上的概率
                dp[x] += pi * dp[x - (1 << i)]; //淘汰第i人后,继续算剩下的人
                tot += pi; //所有情况的概率求和
            }
        dp[x] = (dp[x] + 1) / tot;
    }
    printf("%lf",dp[(1<<n)-1]); //n个1,就是n个人的期望轮数
    return 0;
}

Java代码

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        double[] p = new double[n];
        for (int i = 0; i < n; i++)  p[i] = sc.nextDouble();   
        double[] dp = new double[(1 << 20) + 10];
        int all = (1 << n) - 1;
        for (int x = 0; x <= all; x++) {
            if (Integer.bitCount(x) <= 2)      continue;
            double aw = 1.0, ab = 1.0;
            for (int i = 0; i < n; i++) {
                if ((x & (1 << i)) != 0) {
                    aw *= p[i];
                    ab *= (1 - p[i]);
                }
            }
            double tot = 0.0;
            for (int i = 0; i < n; i++) {
                if ((x & (1 << i)) != 0) {
                    double pi = aw / p[i] * (1 - p[i]) + ab / (1 - p[i]) * p[i];
                    dp[x] += pi * dp[x - (1 << i)];
                    tot += pi;
                }
            }
            dp[x] = (dp[x] + 1) / tot;
        }
        System.out.printf("%.12f\n", dp[all]);
    }
}

Python代码

p = []
dp = []
def bin_count(x):
    ans = 0
    while x:
        ans += 1
        x -= x & (-x)
    return ans
n = int(input())
for _ in range(n):  p.append(float(input()))
for i in range(1 << n):    dp.append(0.0)
for x in range(1 << n):
    if bin_count(x) <= 2:    continue  # 少于等于2人,结束
    aw, ab = 1.0, 1.0
    for i in range(n):
        if x & (1 << i):
           aw *= p[i]
           ab *= (1 - p[i])
    tot = 0.0
    for i in range(n):
        if x & (1 << i):
           pi = aw / p[i] * (1 - p[i]) + ab / (1 - p[i]) * p[i]
           dp[x] += pi * dp[x - (1 << i)]
           tot += pi
    dp[x] = (dp[x] + 1) / tot
print(dp[(1 << n) - 1])

你可能感兴趣的:(算法竞赛快冲300题,动态规划)