[ZOJ3329] One Person Game 题解
One Person Game
题意
\(~~~~\) 有三个骰子,分别有 \(k_1,k_2,k_3\) 个面。 每次掷骰子,如果三个面分别为 \(a,b,c\) 则分数清零,否则加上三个骰子的点数之和。 当分数达到 \(n\) 及以上时结束。求游戏结束的期望步数。
题解
\(~~~~\) 期望 \(dp\) 好题。
\(~~~~\) 套路化地,我们设 \(dp_i\) 表示你有 \(i\) 分时结束的期望步数。则 \(dp_n=0\) ,答案为 \(dp_0\) 。进行倒推。
\(~~~~\) 设: \(p_i\) 表示丢骰子丢出的点数和为 \(i\) 且能加入分数的概率,特殊地,设 \(dp_0\) 表示丢出清零的概率。不难发现它等于 \(\dfrac{1}{k_1k_2k_3}\).
\(~~~~\) 考虑某个状态可以变为哪些状态,则有转移方程:
\(~~~~\) 然后你就会发现转移方程里面有 \(dp_0\) 项,而倒推时 \(dp_0\) 恰恰是最后求出来的。
\(~~~~\) 那怎么办呢?这题不可做,换一道。
\(~~~~\) 观察式子,若设 \(dp_0\) 为未知数,发现它形似一次函数: \(dp_i=a\times dp_{0}+b\) .不妨设 \(a_i,b_i\) 为对应的满足条件的 \(a,b\).
\(~~~~\) 先从特殊情况考虑,当 \(i=n\) 时,\(dp_i=0\),因此 \(a_i=0,b_i=0\);
\(~~~~\) 然后我们把原来的转移方程中的 \(dp_{i+j}\) 也改写一下形式:
\(~~~~\) 再稍稍展开一下:
\(~~~~\) 不难发现:
\(~~~~\) 因此这样转移 \(a,b\) 即可。
\(~~~~\) 再来考虑答案:\(dp_0=a_0 \times dp_0+b_0\) ,因此 \(dp_0=\dfrac{b_0}{1-a_0}\),即答案。
代码
查看代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
double p[20];
double A[505],B[505];
int main() {
int T,n,k1,k2,k3,a,b,c;
scanf("%d",&T);
while(T--)
{
scanf("%d %d %d %d %d %d %d",&n,&k1,&k2,&k3,&a,&b,&c);
p[0]=1.0/k1/k2/k3;
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) continue;
p[i+j+k]+=1;
}
}
}
for(int i=3;i<=k1+k2+k3;i++) p[i]=p[i]/(double)(k1*k2*k3);
for(int i=n;i>=0;i--)
{
A[i]=p[0];B[i]=1.0;
for(int j=3;j<=k1+k2+k3;j++)
{
if(i+j>n) continue;
A[i]+=A[i+j]*p[j];
B[i]+=B[i+j]*p[j];
}
}
printf("%.15f\n",(double)B[0]/(1.0-A[0]));
memset(A,0,sizeof(A));
memset(B,0,sizeof(B));
memset(p,0,sizeof(p));
}
return 0;
}