成环的概率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\n",B[0]/(1-A[0]));
    }
    return 0;
}

 

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

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

posted @ 2018-10-25 22:27  moonfair  阅读(474)  评论(0编辑  收藏  举报