zoukankan      html  css  js  c++  java
  • 概率dp

    https://vjudge.net/contest/264760#problem/B

    数学期望,简单的

    概率从前往后算,期望从后往前算。

    https://blog.csdn.net/morgan_xww/article/details/6775853

    /**
        dp求期望的题。
        题意:
        有三个均匀的骰子,分别有k1,k2,k3个面,初始分数是0,
        当掷三个骰子的点数分别为a,b,c的时候,分数清零,否则分数加上三个骰子的点数和,
        当分数>n的时候结束。求需要掷骰子的次数的期望。
        题解:
        设 E[i]表示现在分数为i,到结束游戏所要掷骰子的次数的期望值。
        显然 E[>n] = 0; E[0]即为所求答案;
        E[i] = ∑Pk*E[i+k] + P0*E[0] + 1; (Pk表示点数和为k的概率,P0表示分数清零的概率)
        由上式发现每个 E[i]都包含 E[0],而 E[0]又是我们要求的,是个定值。
        设 E[i] = a[i]*E[0] + b[i];
        将其带入上面的式子:
        E[i] = ( ∑Pk*a[i+k] + P0 )*E[0] + ∑Pk*b[i+k] + 1;
        显然,
        a[i] = ∑Pk*a[i+k] + P0;
        b[i] = ∑Pk*b[i+k] + 1;
        当 i > n 时:
        E[i] = a[i]*E[0] + b[i] = 0;
        所以 a[i>n] = b[i>n] = 0;
        可依次算出 a[n],b[n]; a[n-1],b[n-1] ... a[0],b[0];
        则 E[0] = b[0]/(1 - a[0]);
    **/
     
    #include <cstdio>
    #include <cstring>
    #include <iostream>
     
    using namespace std;
     
    int main()
    {
        int    nc, n, ks, k1, k2, k3, a, b, c;
        double p0, p[20];
     
        cin >> nc;
        while ( nc-- )
        {
            cin >> n >> k1 >> k2 >> k3 >> a >> b >> c;
            ks = k1 + k2 + k3;
            p0 = 1.0 / (k1*k2*k3);
            memset(p, 0, sizeof(p));
     
            for (int i = 1; i <= k1; i++)
                for (int j = 1; j <= k2; j++)
                    for (int k = 1; k <= k3; k++)
                    {
                        if ( i != a || j != b || k != c )
                            p[i+j+k] += p0;
                    }
     
            double a[520] = {0}, b[520] = {0};
            for (int i = n; i >= 0; i--)
            {
                for (int k = 3; k <= ks; k++)
                {
                    a[i] += a[i+k]*p[k];
                    b[i] += b[i+k]*p[k];
                }
                a[i] += p0;
                b[i] += 1;
            }
            printf("%.15lf
    ", b[0]/(1 - a[0]) );
        }
        return 0;
    }

    /*
    HDU 4035 dp求期望的题。 题意: 有n个房间,由n-1条隧道连通起来,实际上就形成了一棵树, 从结点1出发,开始走,在每个结点i都有3种可能: 1.被杀死,回到结点1处(概率为ki) 2.找到出口,走出迷宫 (概率为ei) 3.和该点相连有m条边,随机走一条 求:走出迷宫所要走的边数的期望值。 设 E[i]表示在结点i处,要走出迷宫所要走的边数的期望。E[1]即为所求。 叶子结点: E[i] = ki*E[1] + ei*0 + (1-ki-ei)*(E[father[i]] + 1); = ki*E[1] + (1-ki-ei)*E[father[i]] + (1-ki-ei); 非叶子结点:(m为与结点相连的边数) E[i] = ki*E[1] + ei*0 + (1-ki-ei)/m*( E[father[i]] + ∑(E[child[i]]) + 1); = ki*E[1] + (1-ki-ei)/m*E[father[i]] + (1-ki-ei)/m*∑(E[child[i]]) + (1-ki-ei); 设对每个结点:E[i] = Ai*E[1] + Bi*E[father[i]] + Ci; 对于非叶子结点i,设j为i的孩子结点,则 ∑(E[child[i]]) = ∑E[j] = ∑(Aj*E[1] + Bj*E[father[j]] + Cj) = ∑(Aj*E[1] + Bj*E[i] + Cj) 带入上面的式子得 (1 - (1-ki-ei)/m*∑Bj)*E[i] = (ki+(1-ki-ei)/m*∑Aj)*E[1] + (1-ki-ei)/m*E[father[i]] + (1-ki-ei) + (1-ki-ei)/m*∑Cj; 由此可得 Ai = (ki+(1-ki-ei)/m*∑Aj) / (1 - (1-ki-ei)/m*∑Bj); Bi = (1-ki-ei)/m / (1 - (1-ki-ei)/m*∑Bj); Ci = ( (1-ki-ei)+(1-ki-ei)/m*∑Cj ) / (1 - (1-ki-ei)/m*∑Bj); 对于叶子结点 Ai = ki; Bi = 1 - ki - ei; Ci = 1 - ki - ei; 从叶子结点开始,直到算出 A1,B1,C1; E[1] = A1*E[1] + B1*0 + C1; 所以 E[1] = C1 / (1 - A1); 若 A1趋近于1则无解... */ #include<stdio.h> #include<string.h> #include<algorithm> #include<iostream> #include<math.h> #include<vector> using namespace std; const int MAXN=10010; const double eps=1e-9;//这里1e-8会WA。设为1e-9和1e-10可以 double k[MAXN],e[MAXN]; double A[MAXN],B[MAXN],C[MAXN]; vector<int>vec[MAXN];//存树 bool dfs(int t,int pre)//t的根结点是pre { int m=vec[t].size();//点t的度 A[t]=k[t]; B[t]=(1-k[t]-e[t])/m; C[t]=1-k[t]-e[t]; double tmp=0; for(int i=0;i<m;i++) { int v=vec[t][i]; if(v==pre)continue; if(!dfs(v,t))return false; A[t]+=(1-k[t]-e[t])/m*A[v]; C[t]+=(1-k[t]-e[t])/m*C[v]; tmp+=(1-k[t]-e[t])/m*B[v]; } if(fabs(tmp-1)<eps)return false; A[t]/=(1-tmp); B[t]/=(1-tmp); C[t]/=(1-tmp); /** Ai = (ki+(1-ki-ei)/m*∑Aj) / (1 - (1-ki-ei)/m*∑Bj); Bi = (1-ki-ei)/m / (1 - (1-ki-ei)/m*∑Bj); Ci = ( (1-ki-ei)+(1-ki-ei)/m*∑Cj ) / (1 - (1-ki-ei)/m*∑Bj); 对于叶子结点 Ai = ki; Bi = 1 - ki - ei; Ci = 1 - ki - ei; **/ return true; } int main() { // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int T; int n; int u,v; int iCase=0; scanf("%d",&T); while(T--) { iCase++; scanf("%d",&n); for(int i=1;i<=n;i++)vec[i].clear(); for(int i=1;i<n;i++) { scanf("%d%d",&u,&v); vec[u].push_back(v); vec[v].push_back(u); } for(int i=1;i<=n;i++) { scanf("%lf%lf",&k[i],&e[i]); k[i]/=100; e[i]/=100; } printf("Case %d: ",iCase); if(dfs(1,-1)&&fabs(1-A[1])>eps) { printf("%.6lf ",C[1]/(1-A[1])); } else printf("impossible "); } }

    https://cn.vjudge.net/contest/227416#problem/C

    https://cn.vjudge.net/contest/227416#problem/E

    2的n次方个球队按照竞赛图踢球,给你任意两只球队之间相互赢球的概率,求最后哪支球队最可能夺冠。

    分析:

    dp[i][j]表示第 i 轮的时候,第 j 支队伍赢的概率。

    可得:

    对于其中位运算,可以用一个二叉树来更直观的理解  (j>>(i-1))^1) 跟 (k>>(i-1)分别表示一个父节点的两个子节点,(或者写一下二进制数来看)

    当(j>>(i-1))^1) == (k>>(i-1)时,表明两个子节点是竞争关系,胜利者将更新到父节点。

    相当于枚举。

    #include<stdio.h>
    #include<string.h>
    #include<math.h>
    #include<algorithm>
    #include <iostream>
    using namespace std;
    const int maxn=150;
    double p[maxn][maxn];
    double dp[8][maxn];
    int main() {
        int n,i,j,k;
        while(scanf("%d",&n)!=EOF) {
        if(n==-1)
        break;
        for(i=0;i<(1<<n);i++)
        for(j=0;j<(1<<n);j++) {
            scanf("%lf",&p[i][j]);
        }
        for(i=0;i<(1<<n);i++) {
            dp[0][i]=1;
        }
        for(i=1;i<=n;i++)
             for(j=0;j<(1<<n);j++)
             {
                 dp[i][j]=0;
                 for(k=0;k<(1<<n);k++)
                 if(((k>>(i-1))^1)==(j>>(i-1)))//这个判断是否属于同一个子树,直接套用了结论
                 dp[i][j]+=dp[i-1][j]*p[j][k]*dp[i-1][k];//与可能比赛的对手进行对决
             }
             double m=0;
             int ans=0;
             for(i=0;i<(1<<n);i++)
             {
                 if(m<dp[n][i])
                 {
                     m=dp[n][i];
                     ans=i;
                  //printf("i==%d
    ",i);
                 }
             }
            ans++;
            printf("%d
    ",ans);
        }
    //system("pause");
        return 0;
    }

    https://www.cnblogs.com/hua-dong/p/8034746.html

    题意:

    师傅被妖怪抓走了。有n个妖怪,每个妖怪有一个固定的战斗力c[],师傅也有一个初始战斗力f0。每天,师傅会随机选择一个妖怪决斗,如果打得赢ft>c[],就可以逃出去,逃出去要t[]天,毕竟超人不会飞;否则,师傅会不甘心,当天他会拿出秘籍练功,将自己变强,f(t+1)=f(t)+c[],第二天寻找下一次机会。问师傅能够逃脱可怕的妖怪,继续追求去印度吃手抓饼的梦想的天数的数学期望day。

    思路:

    设dp[F]是战斗力为F时,逃离的天数期望。(答案是dp[f])。则有公式。

    dp[F]= Σ 1/n * t[i]              ,F>c[[i]

               +∑ 1/n * dp[F+c[i]]    ,F<=c[i]

    经验:

    数学期望题目大多数需要逆推。 此处逆推的方式是记忆化。而且此题,F是单增的,而且有很明显的边界,即F达到大于最大的C[]的时候,就不会再向下面搜索了,所以记忆化搜索很有效。实在想不出顺推的DP,就记忆化逆推吧。不然DP烧脑壳

    #include<cstdio>
    #include<cstdlib>
    #include<iostream>
    #include<cstring>
    #include<cmath>
    using namespace std;
    const double P=(1.0+sqrt(5.0))/2.0;
    const int maxn=200000;
    int c[maxn],t[maxn],n,f;double dp[maxn];
    double dfs(int F)
    {
        if(dp[F]>0) return dp[F];
         for(int i=1;i<=n;i++){
            if(F>c[i]) dp[F]+=1.0*t[i];
            else dp[F]+=dfs(F+c[i])+1.0;
        }
        dp[F]=dp[F]/(1.0*n);return dp[F];
    }
    int main()
    {
        while(~scanf("%d%d",&n,&f)){
           memset(dp,0,sizeof(dp));
           for(int i=1;i<=n;i++){
                scanf("%d",&c[i]);
                t[i]=(int)(1.0*c[i]*c[i]*P);
           }dfs(f);
           printf("%.3lf
    ",dp[f]);
       }return 0;
    }

    https://www.baidu.com/link?url=uccuIyHpfbFaAy2zqStK2ikOfHS41Nn8HoFmhTNeKfuVlX9ZhZdNz1q5dsKs8-bbDif9tjKxEnFc6NT6Mj-XD5aNUDri9tcmv2dCDhqAU_q&wd=&eqid=b1ca22aa00035a27000000055cb728f3   H题状态压缩一下,然后还有一种min-max容斥做法。

    题意:有n种卡片,可以通过买干脆面收集卡片,每包干脆面最多一张卡片,问收集完n种卡片时买的干脆面包数的期望。

    做法:

    把当前手中有的卡片种类状压成s,

    然后dp[s],状态为s时的期望。

    考虑每次买一包干脆面,有三种情况:

    1、已经拥有

    2、没有拥有

    3、没有卡片

    于是dp[s]=dp[s]*(1跟3的概率之和)+dp[s|(1<<i)]*p[i] + 1,i为还没有的卡片编号,p[i]为摸到它的概率

    #include<map>
    #include<string>
    #include<cstring>
    #include<cstdio>
    #include<cstdlib>
    #include<cmath>
    #include<queue>
    #include<vector>
    #include<iostream>
    #include<algorithm>
    #include<bitset>
    #include<climits>
    #include<list>
    #include<iomanip>
    #include<stack>
    #include<set>
    using namespace std;
    double dp[1<<20],p[20];
    int main()
    {
        int n;
        while(cin>>n)
        {
            double sum=1;
            for(int i=0;i<n;i++)
            {
                cin>>p[i];
                sum-=p[i];
            }
            dp[(1<<n)-1]=0;
            for(int i=(1<<n)-2;i>-1;i--)
            {
                double s1=0,s2=0;
                for(int j=0;j<n;j++)
                    if((i>>j)&1)
                        s1+=p[j];
                    else
                        s2+=dp[i|(1<<j)]*p[j];
                dp[i]=(1+s2)/(1-(s1+sum));
            }
            printf("%.4f
    ",dp[0]);
        }
    }

    https://blog.csdn.net/Mr_Alice/article/details/100187465

    题意:

    给你一个 n 个点的有向无环图,保证只有点 1 的入度为 0,点 n 的出度为 0。现在有人从点 1 出发,它能等概率的走到它能走到的下一个点或者停留一天,即有 k个出度时,停下和走任意一条路的概率都为 1/(k+1) ,每一天的消耗为已经经过的天数(包括当天),问你走到点 n的期望消耗为多少。下面代码是记忆化搜索的做法。还可以反向建图,按拓扑序来算结果。

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int N = 1e5 + 10;
    const int inf = 0x3f3f3f3f;
    int t, n, m;
    vector<int> g[N];
    double dp[N], cost[N];
    int vis[N];
    void dfs(int u) {
        vis[u] = 1;
        double sum1 = 0.0, sum2 = 0.0;
        int len = g[u].size();
        if(len == 0) dp[u] = 0.0, cost[u] = 0.0;
        else {
            for(int i = 0; i < len; i++) {
                int v = g[u][i];
                if(!vis[v]) dfs(v);
                sum1 += dp[v];
                sum2 += cost[v];
            }
            dp[u] = (sum1 + len + 1.0) / len;
            cost[u] = (sum2 + dp[u] * (len + 1)) / len;
        }
    //    printf("%d %f %f
    ", u, sum1, sum2);
    }
    
    int main() {
        scanf("%d", &t);
        while(t--) {
            scanf("%d%d", &n, &m);
            memset(vis, 0, sizeof(vis));
            for(int i = 1; i <= n; i++) g[i].clear();
            int u, v;
            for(int i = 1; i <= m; i++) {
                scanf("%d%d", &u, &v);
                g[u].push_back(v);
            }
            dfs(1);
            printf("%.2f
    ", cost[1]);
        }
        return 0;
    }
  • 相关阅读:
    朴素贝叶斯分类算法原理分析与代码实现
    决策树分类算法原理分析与代码实现
    Eclipse Java 调试基本技巧
    Eclipse Java 开发平台实用技巧
    泛型算法
    集合类型的使用示例
    异常
    内部类
    对象复制
    界面设计常用CSS属性
  • 原文地址:https://www.cnblogs.com/downrainsun/p/10718921.html
Copyright © 2011-2022 走看看