B2-L-Lucky Draw(概率DP)

前言:虽然题目加了概率DP,其实是我看大佬的文章加上的,个人是一点不会,但是不影响看懂这道题,就当一个入门引子吧。

题目:传送门

题意:有n个人玩游戏,每个人拥有k条生命。每轮每个人会有p的概率不会死,求最后是平局的概率(平局就是某一轮n个人同时失去最后一条命(吗?))

自己的想法:好家伙这跟高中学的组合什么的肯定有关系,按照这个思路往下想:耗尽k条命至少得进行k局游戏,那么情况就会有很多种,第k局平局,第k+1局平局,……第k+m局平局,把所有情况的概率加一起就是平局的概率咯,当然m是无穷尽的,当m很大(也不用很大,100就差不多)时,概率很小后面就不用再算了。

对于每个人来说:第k+m局耗尽第k条生命的概率:C(k-1,k+m-1) * (1-p)^(k-1)* p^(k+m-k)* (1-p)

那么n个人总的:第k+m局耗尽第k条生命的概率:(C(k-1,k+m-1))^n * (1-p)^(k*n) * p^(m*n)

也就是n个个人情况相乘(?)

最后代码我也敲出来了,前两个样例可以过,差不多就是两个人的时候都对,但是两个以上就不对了。。。。。。想了好久好久也没想明白,可惜啊!

其实看完题解之后我就知道这代码肯定也不好写,因为涉及到求组合,太大了简单的代码肯定过不了。。。复杂的高精度我也不会。。。。但是无奈当两个以上的人参加就不对了,就是想不明白哪里出了问题,哭死了T.T

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>
using namespace std;

int CC(int n, int m) {
    double sum = 1;
    for(int i = 0; i < n; i++) {
        sum *= (1.0 * (m - i)) / (n - i);
    }
    return (int)sum;
}
int main() {
//    freopen("in.txt", "r", stdin);
//    freopen("out.txt", "w", stdout);
    int m, k;
    double p;
    cin >> m >> k >> p;
    double sum = 0;
    for(int i = 0; i <= 1000; i++) {
        sum += pow(CC(k - 1, k +i - 1), m) * pow(p,i*m) * pow(1 - p, k * m);
//        printf("%d %d %lf ",CC(k - 1, i - 1),m,pow(CC(k - 1, i - 1), m));
//        printf("%lf\n", pow(CC(k - 1, k +i - 1), m) * pow(p,i*m) * pow(1 - p, k * m));
    }
    printf("%.9lf\n",sum);
    return 0;
}

哎,费力也没讨到好,真的太难了!

———————分———————————割—————————————线—————————

果然还是得看题解,自己一直想个不停也没用。。已经发现了我的思维漏洞:

平局的理解就错了,平局就是某一轮n个人同时失去最后一条命(×),应该是至少剩下两个人在某一轮同时失去最后一条命,然后游戏结束(√)

2个人只是一个特殊情况,这也解释了为什么我的程序大于2的时候就不对了,因为少考虑很多种情况!

那么这样如果正向做的话,肯定不太可能了,情况实在太多了!

用温老师的话来说用“1-”,就是反着来嘛,假设第m局是决出胜负的最后一局,P(平局) = 1 - P(某个人赢了) = 1 - n * P(第一个人赢了) = 1 - n * P(第一个人在第m局没死&&其他人在前m - 1局就都挂了)

好难,,,悟不开

先上代码吧,大佬写的代码(原文),意思我是明白了,就是代码还没有看懂。。。

#include<bits/stdc++.h> //搜了下才知道这是万能头文件,但也有利有弊,保留下来长长见识
using namespace std;
const int maxn = 1e3 + 10;
double f[maxn][maxn]; //f[i][j]表示i轮后剩余j条生命的概率,一定要注意是i轮后而不是第i轮,二者完全不同
int main()
{
//    freopen("out.txt", "w", stdout);
    int n, m;
    double p;
    scanf("%d%d%lf", &n, &m, &p);
    f[0][m] = 1;
    for(int i = 1; i < maxn; ++i)
    {
        for(int j = m; j > 0; --j)
            f[i][j] = p * f[i - 1][j] + (1 - p) * f[i - 1][j + 1];
//i轮后剩余j条生命的概率 = (i - 1轮后剩余j条生命的概率 * p) + (i - 1轮后剩余j + 1条生命的概率 * (1-p))
//这里的思想精髓要学到!没用乘幂却实现了比乘幂还好的效果!!
        f[i][0] = f[i - 1][0] + (1 - p) * f[i - 1][1]; //生命耗尽时不用乘以p(所以用的是--j)
    }
    double ans = 0;
    for(int i = 1; i < maxn; ++i) //1000次之后的概率对答案影响很小
    {
        ans += (f[i][0] - f[i - 1][0]) * pow(f[i - 1][0], n - 1); //计算在第i轮死亡并且其他人都在前i-1轮死亡的概率
//f[i][0] - f[i - 1][0] -> 前i轮生命耗尽的概率 - 前i-1轮生命耗尽的概率 = 恰好第i轮耗尽生命的概率。其实就是上面那个移项-_-
//但是,为什么要他死?不是只剩一个人就他赢吗?是的,但是不死的话游戏的局数i怎么定下来呢?
//这是从概率的角度需要让他比倒数第二个迟一轮耗尽,虽然实际上不管他赢还是输比赛都会结束。
//pow(f[i - 1][0], n - 1) -> 其余n-1个人前i-1轮生命耗尽的概率(分步乘法原理)
//        printf("%lf\n",(f[i][0] - f[i - 1][0]) * pow(f[i - 1][0], n - 1));
    }
    printf("%.10f\n", 1 - n * ans);
    return 0;
}

 简而言之这代码就是实现1 - n * P(第一个人在第m局没死&&其他人在前m - 1局就都挂了)的过程

放一放再继续想,太累了。。

——————————————补——————————————充—————————————

呼,终于看得差不多了,在大佬的代码里加了很多解释的注释,总算弄懂了实现的过程,编程是真的牛逼。。。只是我还不会利用,这就是数学思维和计算机思维的碰撞结合(ಡωಡ),把自己想的东西用代码来实现才算是真正学会,还得加油啊!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值