成環的概率dp(初級) 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; }
博主新手上路,覺得不錯的能否賞個贊或關注?
覺得有寫得不好的地方也歡迎大家指正,我會及時修改!