石头剪刀布
题意:
桌面上有
r
r
r 个石头,
s
s
s 个剪刀和
p
p
p 个布。
每次等概率选择两个(种类)不同的物品进行剪刀石头布,将输掉的物品移出桌面。
求最后桌面上只剩下石头的概率,和只剩下剪刀的概率,和只剩下布的概率。保留八位小数。
解法:
用
dp
[
i
]
[
j
]
[
k
]
\text{dp}[i][j][k]
dp[i][j][k] 表示剩余
i
i
i 个石头,
j
j
j 个剪刀和
k
k
k 个布的概率。则状态转移方程为
dp
[
i
−
1
]
[
j
]
[
k
]
+
=
d
p
[
i
]
[
j
]
[
k
]
∗
P
{
rock and paper
}
\text{dp}[i-1][j][k]+=dp[i][j][k]*P\{\text{rock and paper}\}
dp[i−1][j][k]+=dp[i][j][k]∗P{rock and paper} 其中
P
{
rock and paper
}
=
k
i
i
j
+
j
k
+
k
i
P\{\text{rock and paper}\}=\frac{ki}{ij+jk+ki}
P{rock and paper}=ij+jk+kiki 其他情况类似。
参考代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
const int MAXN=1e2+10;
double dp[MAXN][MAXN][MAXN];
int r,s,p;
inline double prob(int a,int b,int c){
return 1.0*a*b/(a*b+b*c+c*a);
}
void calc(int i,int j,int k){
if (i&&k) dp[i-1][j][k]+=dp[i][j][k]*prob(i,k,j);
if (i&&j) dp[i][j-1][k]+=dp[i][j][k]*prob(i,j,k);
if (j&&k) dp[i][j][k-1]+=dp[i][j][k]*prob(j,k,i);
}
void solve(){
for (int x=0;x<r+s+p;x++)
for (int i=0;i<=std::min(x,r);i++)
for (int j=0;j<=std::min(x-i,s);j++)
calc(r-i,s-j,p-(x-i-j));
double rr=0,ss=0,pp=0;
for (int i=1;i<=r;++i)
rr+=dp[i][0][0];
for (int i=1;i<=s;++i)
ss+=dp[0][i][0];
for (int i=1;i<=p;++i)
pp+=dp[0][0][i];
printf("%.8lf %.8lf %.8lf\n",rr,ss,pp);
}
int main(){
// freopen("k.in","r",stdin);
// freopen("k.out","w",stdout);
scanf("%d%d%d",&r,&s,&p);
memset(dp,0,sizeof(dp));
dp[r][s][p]=1;
solve();
}
v and x
题意:
有两个变量
v
,
x
v,x
v,x ,初始全为
0
0
0 。每一次操作有
p
1
p_1
p1 的概率将
v
v
v 自增
1
1
1 ,有
p
2
p_2
p2 的概率将
x
x
x 自增
v
v
v 。当
x
⩾
k
x\geqslant k
x⩾k 时结束操作。问
x
x
x 的期望。保留六位小数。
解法:
当
v
+
x
⩾
k
v+x\geqslant k
v+x⩾k 时,往后只有两种情况:一,
v
v
v 自增,然后继续;二,
x
x
x 自增,结束。则该情况下的期望为
E
(
x
)
=
∑
i
=
0
∞
p
1
i
p
2
(
v
+
x
+
i
)
=
p
2
∑
i
=
0
∞
(
p
1
i
(
v
+
x
)
+
i
p
1
i
)
=
p
2
(
v
+
x
1
−
p
1
+
p
1
(
1
−
p
1
)
2
)
\begin{aligned}E(x)=&\sum_{i=0}^{\infty}p_1^ip_2(v+x+i)\\=&p_2\sum_{i=0}^{\infty}\left(p_1^i(v+x)+ip_1^i\right)\\=&p_2\left(\frac{v+x}{1-p_1}+\frac{p_1}{(1-p_1)^2}\right)\end{aligned}
E(x)===i=0∑∞p1ip2(v+x+i)p2i=0∑∞(p1i(v+x)+ip1i)p2(1−p1v+x+(1−p1)2p1)
当 v + x < k v+x<k v+x<k 时,期望分为两部分,即 E ( x ) = p 1 E ( x ) ∣ v = v + 1 + p 2 E ( x ) ∣ x = x + v E(x)=p_1E(x)\vert_{v=v+1}+p_2E(x)\vert_{x=x+v} E(x)=p1E(x)∣v=v+1+p2E(x)∣x=x+v
此外还有边界条件的处理。因为 E ( x ) ∣ v = 0 , x = 0 E(x)\vert_{v=0,x=0} E(x)∣v=0,x=0 的状态转移为 E ( x ) ∣ v = 0 , x = 0 = p 1 E ( x ) ∣ v = 1 , x = 0 + p 2 E ( x ) ∣ v = 0 , x = 0 E(x)\vert_{v=0,x=0}=p_1E(x)\vert_{v=1,x=0}+p_2E(x)\vert_{v=0,x=0} E(x)∣v=0,x=0=p1E(x)∣v=1,x=0+p2E(x)∣v=0,x=0 如果直接迭代会没有结果。但是通过人为的运算,就用刚才的那个“无限递归式”,我们得到 E ( x ) ∣ v = 0 , x = 0 = p 1 1 − p 2 E ( x ) ∣ v = 1 , x = 0 E(x)\vert_{v=0,x=0}=\frac{p_1}{1-p_2}E(x)\vert_{v=1,x=0} E(x)∣v=0,x=0=1−p2p1E(x)∣v=1,x=0 则下面只需计算 E ( x ) ∣ v = 1 , x = 0 E(x)\vert_{v=1,x=0} E(x)∣v=1,x=0 即可。
参考代码:
#include <cstdio>
#include <cstring>
const int MAXN=1e3+10;
double dp[MAXN][MAXN];
int k,pa,pb;
double p1,p2;
double solve(int v,int x){
if (dp[v][x]) return dp[v][x];
if (v+x>=k) return dp[v][x]=p2*((v+x)/(1-p1)+p1/(1-p1)/(1-p1));
return dp[v][x]=p1*solve(v+1,x)+p2*solve(v,x+v);
}
int main(){
memset(dp,0,sizeof(dp));
scanf("%d%d%d",&k,&pa,&pb);
p1=1.0*pa/(pa+pb);
p2=1.0*pb/(pa+pb);
printf("%.6lf\n",p1*solve(1,0)/(1-p2));
return 0;
}
Collecting Bugs
题意:
某软件有
s
s
s 个子系统,会产生
n
n
n 种bug。某人一天能发现一个bug,不同子系统和种类中发现bug是等可能性的。现发现了
n
n
n 种bug,问每个子系统都发现bug的天数的期望。
解法:
用
dp
[
i
]
[
j
]
\text{dp}[i][j]
dp[i][j] 表示已找到
i
i
i 种
j
j
j 个子系统的bug后剩余天数的期望,则显然有
dp
[
n
]
[
s
]
=
0
\text{dp}[n][s]=0
dp[n][s]=0 ,题中所求为
dp
[
0
]
[
0
]
\text{dp}[0][0]
dp[0][0] 。对于一个状态
dp
[
i
]
[
j
]
\text{dp}[i][j]
dp[i][j] ,当新发现了一个bug时,可能会出现:
- 发现已有分类和已有子系统的bug,概率为 p 1 = i n ⋅ j s p_1=\frac{i}{n}\cdot\frac{j}{s} p1=ni⋅sj
- 发现已有的分类未有子系统的bug,概率为 p 2 = i n ⋅ s − j s p_2=\frac{i}{n}\cdot\frac{s-j}{s} p2=ni⋅ss−j
- 发现未有的分类已有子系统的bug,概率为 p 3 = n − i n ⋅ j s p_3=\frac{n-i}{n}\cdot\frac{j}{s} p3=nn−i⋅sj
- 发现未有分类和未有子系统的bug,概率为 p 4 = n − i n ⋅ s − j s p_4=\frac{n-i}{n}\cdot\frac{s-j}{s} p4=nn−i⋅ss−j
则有 dp [ i ] [ j ] = 1 + p 1 dp [ i ] [ j ] + p 2 dp [ i ] [ j + 1 ] + p 3 dp [ i + 1 ] [ j ] + p 4 dp [ i + 1 ] [ j + 1 ] \text{dp}[i][j]=1+p_1\text{dp}[i][j]+p_2\text{dp}[i][j+1]+p_3\text{dp}[i+1][j]+p_4\text{dp}[i+1][j+1] dp[i][j]=1+p1dp[i][j]+p2dp[i][j+1]+p3dp[i+1][j]+p4dp[i+1][j+1]
整理得状态转移方程为 dp[i][j]=(i*(s-j)*dp[i][j+1]+(n-i)*j*dp[i+1][j]+(n-i)*(s-j)*dp[i+1][j+1]+n*s)/(n*s-i*j)
。
由于是POJ的题目,最后需要用%f
输出而不是%lf
。
参考代码:
#include <cstdio>
const int MAXN=1e3+10;
double dp[MAXN][MAXN];
int n,s;
double solve(){
dp[n][s]=0;
for (int i=n;i>=0;i--){
for (int j=s;j>=0;j--){
if (i==n&&j==s) continue;
dp[i][j]=(i*(s-j)*dp[i][j+1]+(n-i)*j*dp[i+1][j]+(n-i)*(s-j)*dp[i+1][j+1]+n*s)/(n*s-i*j);
}
}
return dp[0][0];
}
int main(){
while(~scanf("%d%d",&n,&s)){
printf("%.4lf\n",solve());
}
return 0;
}