成環的概率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;
}

 

 博主新手上路,覺得不錯的能否賞個贊或關注?

 覺得有寫得不好的地方也歡迎大家指正,我會及時修改!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章