概率动规小专题

石头剪刀布

题意:
桌面上有 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[i1][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 
#include 
#include 
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 xk 时结束操作。问 x x x 的期望。保留六位小数。

解法:
v + x ⩾ k v+x\geqslant k v+xk 时,往后只有两种情况:一, 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=0p1ip2(v+x+i)p2i=0(p1i(v+x)+ip1i)p2(1p1v+x+(1p1)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=1p2p1E(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 
#include 
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时,可能会出现:

  1. 发现已有分类和已有子系统的bug,概率为 p 1 = i n ⋅ j s p_1=\frac{i}{n}\cdot\frac{j}{s} p1=nisj
  2. 发现已有的分类未有子系统的bug,概率为 p 2 = i n ⋅ s − j s p_2=\frac{i}{n}\cdot\frac{s-j}{s} p2=nissj
  3. 发现未有的分类已有子系统的bug,概率为 p 3 = n − i n ⋅ j s p_3=\frac{n-i}{n}\cdot\frac{j}{s} p3=nnisj
  4. 发现未有分类和未有子系统的bug,概率为 p 4 = n − i n ⋅ s − j s p_4=\frac{n-i}{n}\cdot\frac{s-j}{s} p4=nnissj

则有 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 
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;
}

你可能感兴趣的:(算法笔记,ACM,C/C++)