概率论应用

概率论应用(概率期望dp)


基础知识

条件概率公式
在事件B已经发生的条件下,A发生的概率 \(P(A|B) = \frac{P(AB)}{P(B)}\)

全概率公式
\(P(B) = P(BΩ) = P[B(\bigcup_{i=1}^{n}A_i)] = P(\bigcup_{i=1}^{n}A_i B) = \sum_{i=1}^{n}P(A_iB)=\sum_{i=1}^{n}P(A_i)P(B|A_i)\)

贝叶斯公式

\(P(A_i | B) = \frac{P(A_iB)}{P(B)} = \frac{P(A_i)P(B|A_i)}{P(B)}\)

\(P(B) = \sum_{i=1}^{n}P(A_i)P(B|A_i)\)

\(P(A_i | B) = \frac{P(A_i)P(B|A_i)}{\sum_{i=1}^{n}P(A_i)P(B|A_i)}\)

期望

  • \(期望的线性性质 随机变量X,Y E(X+Y) = E(X) + E(Y)\)
  • 把所有情况不重复的,不遗漏的分成若干类,每类计算期望,按每类的概率加权求和

LightOj 1027

题意: 有n扇门,每次你可以选择其中一扇。xi为负值的门带你abs(xi)后又回到原点。xi为正值

的门则带你离开迷宫。每次你都没有经验没有记忆。选择每扇门的概率相等。求走出迷宫

的时间期望值。

思路:
解法一: 假设走出迷宫的期望值为E,
选择到正数的门,走出的期望值为 $T1 \frac{1}{n} $
选择到负数的门,走回到起点(起点走出的期望为E),所以走出的期望值为 \((T2 + E) \frac{1}{n}\)
于是\(E =T1 \frac{1}{n}+(T2 + E) \frac{1}{n}\)
化简一下 $E = \frac{\sum{T_i}}{cnt正} $

解法二:
走出的时间期望值E = 走出去次数的期望 × 平均时间
假设正的门有\(k\)个,则走出的概率为\(\frac{k}{n}\),于是走出去的次数的期望为\(\frac{n} {k}\)(列式子,错位相减,求极限即可得出)
平均时间 = \(\frac{\sum{T_i}}{n}\)
E = \(\frac{n} {k}\frac{\sum{T_i}}{n} = \frac{\sum{T_i}}{k}\)

#include<bits/stdc++.h>
#define LL long long

using namespace std;

int gcd(int x,int y){
    return y  == 0?x:gcd(y,x%y);
}

int main()
{
    int T;
    int cas = 1;
    cin>>T;
    while(T--){
        int n;
        int x, total = 0, cnt = 0;
        scanf("%d",&n);
        for(int i = 0;i <n;i++){
            scanf("%d",&x);
            if(x < 0) total += -x;
            else total += x,cnt++;
        }
        int g = gcd(cnt, total);
        printf("Case %d: ",cas++);
        if(!cnt) printf("inf\n");
        else printf("%d/%d\n",total / g, cnt / g);
    }
    return 0;
}

Light 1030

题意:给定n个格子以及每个格子上的gold值,你从第一个格子出发,每次掷1-6的骰子,根据掷出的值前进。若当前位置 + 掷出的值 > n,则重新掷骰子,到达第n个格子游戏结束。问你获得gold值的期望。

思路: 由于每个格子是相互独立的,分别计算就好了,
定义dp[i]为到达格子i的概率,则期望值就是\(\sum_{i=1}^{n}{dp[i]a[i]}\)

#include<bits/stdc++.h>
#define LL long long
#define db long double
using namespace std;
const int N = 1e2 + 10;
double dp[N];
int main()
{
    int T;
    int cas = 1;
    cin>>T;
    while(T--){
        int n;
        scanf("%d",&n);
        memset(dp, 0, sizeof(dp));
        double ans = 0;
        for(int i = 1;i <= n;i++){
            int x;
            scanf("%d",&x);
            if(i == 1) dp[i] = 1;
            else{
                int p = max(1,i - 6);
                for(int j = p;j < i;j++) dp[i] += dp[j] / min(6,n - j);
            }
            ans += x * dp[i];
        }
        printf("Case %d: ",cas++);
        printf("%.12lf\n",ans);
    }
    return 0;
}

Light Oj 1038

题意:给一个数D,每次可以变成它的因子,求变成1的期望次数 \(1<=D<=10^5\)

思路: \(设dp[x]为x变成1的期望次数, x的因子个数为cnt,则dp[x] = \frac{dp[x] + \sum_{d|x,d!=x}{dp[d]} + cnt}{cnt}\)
解得 $ dp[x]= \frac{\sum_{d|x,d!=x}{dp[d]} + cnt}{cnt-1}$
\(预处理dp数组即可,复杂度nlogn\)

const int N = 1e5 + 10;
double dp[N];
void init(){
    dp[1] = 0;
    for(int i = 2;i < N;i++){
        int cnt = 0;
        dp[i] = 0;
        for(int j = 1;j * j <= i;j++){
            if(i % j == 0){
                cnt++;
                if(i / j != j) cnt++;
            }
        }
        for(int j = 1;j * j <= i;j++){
            if(i % j == 0){
                dp[i] += dp[j];
                if(i / j != j && j != 1){
                    dp[i] += dp[i / j];
                }
            }
        }
        dp[i] += cnt;
        dp[i] /= cnt - 1;
    }
}
int main()
{
    init();
    int T;
    int cas = 1;
    cin>>T;
    while(T--){
        int n;
        scanf("%d",&n);
        printf("Case %d: ",cas++);
        printf("%.12lf\n",dp[n]);
    }
    return 0;
}  

Light 1248

题意:给一枚n面的骰子,求期望次数使得所有n面都至少朝上过一次。

思路: \(dp[i][j] 表示i面的骰子的j面至少朝上过一次的期望次数\)
\(dp[i][1] = 1\)
\(dp[i][j] = \frac{i-j+1}{i}(dp[i][j]+1) + \frac{j-1}{i}(dp[i][j-1] + 1) (2<=j<=i)\)
化简得\(dp[i][j] = dp[i][j-1] + \frac{i}{j-1}\)
于是答案就等于\(\sum_{i=1}^{n}\frac{n}{i}\)

Light Oj 1079

题意:一个强盗想要抢劫几家银行,每家银行有一定的存款数,且每抢劫一家银行都有一定概率被抓获,问在给定的安全概率下,强盗所能抢劫到的最大钱数。且抢劫各个银行被抓的概率是相互独立的。

思路: 容量为浮点数的背包问题
由于这里钱的总数比较小,把钱看成容量,dp[i]表示抢到钱i的最小被抓概率
从大到小枚举一下最抓概率小于安全概率的就是答案
若钱也为浮点数就不能这样解了,这篇博客讲了连续值背包的求解问题

const int N = 1e4 + 10;
double dp[N];
double P;
int n;
double p[N];
int w[N];
int main()
{
    int T;
    int cas = 1;
    cin>>T;
    while(T--){
        scanf("%lf%d",&P,&n);
        int sum = 0;
        for(int i =1;i <= n;i++){
                scanf("%d%lf",w + i,p + i);
                sum += w[i];
        }
        for(int i = 0;i <= sum;i++) dp[i] = 1000000;
        dp[0] = 0;
        for(int i = 1;i <= n;i++){
            for(int j = sum;j >= w[i];j--)
                dp[j] = min(dp[j],p[i] + (1 - p[i]) * dp[j - w[i]]);
        }
        int ans = 0;
        for(int i = sum;i >= 0;i--) if(dp[i] < P) {
            ans = i;
            break;
        }
        printf("Case %d: %d\n",cas++, ans);
    }
    return 0;
}

Light Oj 1265

题意: 有一个人在荒岛上求生,岛上有t只老虎,d只鹿
每天随机的会有两只动物相遇
>- 老虎和老虎相遇,相互残杀,最后都会死亡
>- 老虎和人相遇,人被老虎吃了
>- 老虎和鹿相遇,鹿被老虎吃了
>- 鹿和鹿相遇,什么也没发生
>- 人和鹿相遇,人可以选择吃或者不吃鹿

  问你当人能肯定自己一定能存活下来的最大概率是多少?

思路: 首先奇数只老虎,人一定不能存活。
偶数只老虎相互残杀最后变成0,那其实也就是求这个老虎相互残杀变成0只的概率
从长远来看,很久很久以后,鹿一定被老虎吃光了,那么就只剩人和老虎了,这个时候就相当于每次选两次老虎直到为0,即C(t,2) / C(t + 1,2)的累乘,因为这里求的是概率,所以若干年后概率是不会变的,如果求的是年数的期望,就不可行了。

另一种更好想的做法就是dp[i][j]表示剩i只老虎j只鹿,人存活的概率
考虑转移每次要么死两只老虎,要么死一只鹿,死的这只鹿可以是老虎杀的,也可以是人杀的

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 1001;
double dp[N][1001];
double C(int n){
    if(n < 2) return 0;
    return 1.0 * n * (n - 1) / 2;
}
int main()
{
    int T, cas = 1;
    cin>>T;
    while(T--){
        int t, d;
        scanf("%d%d",&t,&d);
        printf("Case %d: ",cas++);
        if(t % 2){
            printf("0\n");
            continue;
        }
        /*memset(dp, 0, sizeof(dp));
        for(int i = 0;i <= d;i++) dp[0][i] = 1;
        for(int i = 1;i <= t;i++){
            for(int j = 0;j <= d;j++){
                double a = 0,b = 0;
                int suma = C(i+j+1) - C(j) - j, sumb = suma + j;
                if(j) a += i * j * dp[i][j-1],b += i * j * dp[i][j-1] + j * dp[i][j-1];
                if(i>=2) a += C(i) * dp[i - 2][j],b += C(i) * dp[i - 2][j];
                if(suma > 0) a /= suma;
                if(sumb > 0) b /= sumb;
                dp[i][j] = max(a, b);
            }
        }*/
        double ans = 1;
        while(t) ans *= 1.0 * (t - 1) / (t + 1),t -= 2;
        printf("%.12lf\n",ans);
    }
    return 0;
}
posted @ 2017-07-17 21:28  jiachinzhao  阅读(556)  评论(0编辑  收藏  举报