• 成环的概率dp(初级) zoj 3329


    原题地址https://vjudge.net/problem/ZOJ-3329

    题目大意

      有三个骰子,分别有k1,k2,k3个面,初始分数是0。第i骰子上的分数从1道ki。当掷三个骰子的点数分别为a,b,c的时候,分数清零,否则分数加上三个骰子的点数和,当分数>n的时候结束。求需要掷骰子的次数的期望。

    (0<=n<= 500,1<K1,K2,K3<=6,1<=a<=K1,1<=b<=K2,1<=c<=K3)

    思路

      如果设当前分数为 i ,且再有 dp[ i ] 次投掷可以达到分数 n

      设该次投出的点数为 k 

      那么容易写出状态转移方程  dp[ i ] = ∑ ( dp[ i+k ] * p[ k ] )  +  dp[ 0 ] * p[ 0 ] + 1 

      因为从当前状态开始,再投一次( 这就是式子中 +1 的由来 ) 可能到达的分数有 k 种,概率分别为 p[ 1 ] 到 p[ k ] (当然, p[ 1 ] , p [ 2 ]已被初始化为 0 .

      除此之外 ,也可能投出 k1=a,k2=b,k3=c 的组合,因此要加上 dp[ 0 ] * p[ 0 ]  这一项 .

      至此,我们得到了转移方程

      但是,经过观察我们可以发现它实际上是不能用的

      大凡可以使用的方程,必定是从一个方向推向另一个方向,要么从小到大(正推) ,要么从大到小(逆推)

      但是这个方程中,右边的项同时包含了比 i 大的( dp[ i+k ] ) 和比 i 小的( dp[ 0 ] )

      这就使dp 陷入一个自身依赖自身的环中

      一般遇到这种情况,我们会采取高斯消元法解方程来解决

      但因为博主太菜了,还不会(会补的,会补的......)

      同时,这道题中阻碍我们进行 dp 的只有 dp[ 0 ] 这一项

      因此我们采取将 dp[ 0 ] 设为未知数的方法

      

      注意到,每个 dp[ i ] 都含有相同的元素 dp[ 0 ]

      则 dp[ i ] 是 dp [ 0 ] 的一个线性组合( 因为没有出现 dp[ 0 ] 的高次幂)

      因此可以将转移方程写成  dp[ i ] = dp[ 0 ] * a[ i ]+b[ i ]  ············( 1 )

      于是就有 dp[ i+k ] = dp[0] * a[ i+k ]+b[ i+k ]

      把这个式子带入原来的转移方程得到 dp[ i ]  = dp[ 0 ] * p[ 0 ] + ∑( dp[ i+k ] * p[ i+k ] )  +  1

      再将这个式子中的 dp[ 0 ] 分离出来,化成与式 ( 1 ) 相同的形式    dp[ i ]  = dp[ 0 ] * (     ∑ ( a[ i+k ] * p[ i+k ] ) + p[ 0 ]     )    +     (     ∑( b[ i+k ] * p[ i+k ] ) + 1    )

      我们把( 1 )式拉下来,让你看得更清楚:                                       dp[ i ]   = dp[0]              *                 a[ i ]                             +                          b[ i ]  

      因此,我们得到了新的,关于 a,b 的方程:

      

        a[ i ] = ∑ (   a[ i+k ] * p[ i+k ] ) + p[ 0 ]

                   b[ i ] =∑ (  b[ i+k ] * p[ i+k ] ) + 1

      我们惊喜地发现,这是两个状态转移方程

      我们可以通过逆推得到 a[ 0 ]b[ 0 ]

      还记得式(1)吗?如果我们把它的 i 取成 0 ,就得到:    

          dp[ 0 ] = dp[ 0 ] * a[ 0 ]+b[ 0 ]

      我们终于能够解出 dp[ 0 ]

      而这也正是本题的答案

    下边附上kuagnbin 大大的代码:

    ·  

    #include<stdio.h>
    #include<string.h>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    
    double A[600],B[600];
    double p[100];
    int main()
    {
        int T;
        int k1,k2,k3,a,b,c;
        int n;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d%d%d%d%d%d",&n,&k1,&k2,&k3,&a,&b,&c);
            double 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;
            memset(A,0,sizeof(A));
            memset(B,0,sizeof(B));
            for(int i=n;i>=0;i--)
            {
                A[i]=p0;B[i]=1;
                for(int j=1;j<=k1+k2+k3;j++)
                {
                    A[i]+=A[i+j]*p[j];
                    B[i]+=B[i+j]*p[j];
                }
            }
            printf("%.16lf
    ",B[0]/(1-A[0]));
        }
        return 0;
    }

     博主新手上路,觉得不错的能否赏个赞或关注?

     觉得有写得不好的地方也欢迎大家指正,我会及时修改!

  • 相关阅读:
    20201022-1 每周例行报告
    2020高级软件工程“领跑衫”获奖感言
    20201015-3 每周例行报告
    20201008-1 每周例行报告
    竞拍作业
    20201207-总结-作业
    20201126-1每周例行报告
    20201120-1每周例行报告
    20201112 -1每周例行报告
    20201105-1例行报告
  • 原文地址:https://www.cnblogs.com/moonfair/p/9853383.html
Copyright © 2020-2023  润新知