There is a very simple and interesting one-person game. You have 3 dice, namelyDie1, Die2 and Die3. Die1 hasK1 faces. Die2 has K2 faces.Die3 has K3 faces. All the dice are fair dice, so the probability of rolling each value, 1 toK1, K2, K3 is exactly 1 /K1, 1 / K2 and 1 / K3. You have a counter, and the game is played as follow:
Calculate the expectation of the number of times that you cast dice before the end of the game.
Input
There are multiple test cases. The first line of input is an integer T (0 <T <= 300) indicating the number of test cases. Then T test cases follow. Each test case is a line contains 7 non-negative integersn, K1, K2, K3,a, b, c (0 <= n <= 500, 1 < K1,K2, K3 <= 6, 1 <= a <= K1, 1 <=b <= K2, 1 <= c <= K3).
Output
For each test case, output the answer in a single line. A relative error of 1e-8 will be accepted.
Sample Input
2 0 2 2 2 1 1 1 0 6 6 6 1 1 1
Sample Output
1.142857142857143 1.004651162790698Author: CAO, Peng
Source: The 7th Zhejiang Provincial Collegiate Programming Contest
题意:有三个骰子,分别有k1,k2,k3个面。
每次掷骰子,如果三个面分别为a,b,c则分数置0,否则加上三个骰子的分数之和。
当分数大于n时结束。求游戏的期望步数。初始分数为0
设dp[i]表示达到i分时到达目标状态的期望,pk为投掷k分的概率,p0为回到0的概率
则dp[i]=∑(pk*dp[i+k])+dp[0]*p0+1;
开始还在纠结i+k>n怎么办。其实根本不用考虑。因为i+k>n时期望为0完全不用考虑了。
还有注意分数大于n时才结束。等于n时不会结束。
因为转移方程和dp[0]有关系,而且dp[0]就是我们所求,为常数
设dp[i]=A[i]*dp[0]+B[i];
代入上述方程右边得到:
dp[i]=∑(pk*A[i+k]*dp[0]+pk*B[i+k])+dp[0]*p0+1
=(∑(pk*A[i+k])+p0)dp[0]+∑(pk*B[i+k])+1;
明显A[i]=(∑(pk*A[i+k])+p0)
B[i]=∑(pk*B[i+k])+1
先递推求得A[0]和B[0].
那么 dp[0]=B[0]/(1-A[0]);
期望DP关键在于找好状态。好的状态可以让状态转移的很清晰方便处理。
详细见代码:
#include <iostream> #include<stdio.h> #include<string.h> using namespace std; double A[550],B[550],p[25]; int main() { int cas,i,j,k,k1,k2,k3,n,a,b,c,lim; double pp; scanf("%d",&cas); while(cas--) { memset(A,0,sizeof A); memset(B,0,sizeof B); memset(p,0,sizeof p); scanf("%d%d%d%d%d%d%d",&n,&k1,&k2,&k3,&a,&b,&c); lim=k1+k2+k3; pp=1.0/k1/k2/k3; for(i=1;i<=k1;i++) for(j=1;j<=k2;j++) for(k=1;k<=k3;k++) if(i!=a||j!=b||k!=c) p[i+j+k]+=pp; for(i=n;i>=0;i--) { A[i]=pp,B[i]=1; for(j=3;j<=lim;j++) { A[i]+=A[i+j]*p[j]; B[i]+=B[i+j]*p[j]; } } printf("%.15lf\n",B[0]/(1-A[0])); } return 0; }
这题如果用高斯法做就超时了。。。下面高斯法代码。
#include <iostream> #include<stdio.h> #include<string.h> #include<math.h> using namespace std; const int maxn=510; const double eps=1e-11; double mat[maxn][maxn],p[25]; bool guass(int lim)//高斯消元 { int row,i,j,id; double maxx,var; for(row=0;row<lim;row++)//遍历行。重点在mat[row][row]先找此处最大系数。然后把以下方程的对应未知数消去 { maxx=fabs(mat[row][row]); id=row;//id记录位置 for(i=row+1;i<lim;i++) { if(fabs(mat[i][row])>maxx) { maxx=fabs(mat[i][row]);//注意是绝对值大 id=i; } } if(maxx<eps) return false; if(id!=row)//如果就是当前处理行就不用交换 { for(i=row;i<=lim;i++)//交换最大行和当前行 swap(mat[row][i],mat[id][i]); } for(i=row+1;i<lim;i++)//遍历行。所以<lim.把当前处理行以下的mat[row][row]变量消去。 { if(fabs(mat[i][row])<eps)//本来就为0就不用处理了 continue; var=mat[i][row]/mat[row][row]; for(j=row;j<=lim;j++)//包括扩展矩阵所以c<=lim。 mat[i][j]-=mat[row][j]*var; } } for(i=lim-1;i>=0;i--)//从最后一个系数开始 { for(j=i+1;j<lim;j++) mat[i][lim]-=mat[i][j]*mat[j][j]; mat[i][i]=mat[i][lim]/mat[i][i];//现在系数矩阵的对角线用于记录答案。 } return true; } int main() { int cas,i,j,k,k1,k2,k3,n,a,b,c,lim; double pp; scanf("%d",&cas); while(cas--) { memset(mat,0,sizeof mat); memset(p,0,sizeof p); scanf("%d%d%d%d%d%d%d",&n,&k1,&k2,&k3,&a,&b,&c); lim=k1+k2+k3; pp=1.0/k1/k2/k3; for(i=1;i<=k1;i++) for(j=1;j<=k2;j++) for(k=1;k<=k3;k++) if(i!=a||j!=b||k!=c) p[i+j+k]+=pp; for(i=0;i<=n;i++) { mat[i][0]=-pp; for(j=3;j<=lim;j++) mat[i][i+j]=-p[j]; mat[i][i]+=1; mat[i][n+1]=1; } guass(n+1); printf("%.15lf\n",mat[0][0]); } return 0; }