ZOJ 3329 One Person Game

思路

典型的一类概率dp问题
逆推,用dp[i]表示从n到i的期望次数,p[i]表示获得i分数的概率,px表示分数清零的概率
容易想到题目中的转移方程为(dp[j]=sum_{i}^{sumk}dp[j+i]p[i]+dp[0]px+1)
显然从dp[n]开始,要求dp[0]
可是每个状态的转移都包含了dp[0],转移出现环了怎么办?
高斯消元?可是n=500,T=300,根本过不去
发现并不每个都互相依赖,而是都依赖于dp[0],那我们找出dp[x]与dp[0]的关系,问题似乎就可以解决了
由于期望的线性性,我们尝试列出一个线性的方程组的形式(或者说就是给dp[0]配个系数)

[dp[x]=a_xdp[0]+b_x ]

把它带入转移方程中,得到

[dp[j]=sum_{i}^{sumk}(a_{i+j}dp[0]+b_{i+j})p[i]+dp[0]px+1 ]

尝试把它变形回原来的形式

[dp[j]=dp[0](px+sum_{i}^{sumk}a_{i+j}p[i])+sum_{i}^{sumk}b_{i+j}+1 ]

把他和原式(dp[x]=a_xdp[0]+b_x)对应,得到

[a[j]=(px+sum_{i}^{sumk}a_{i+j}p[i]) ]

[b[j]=1+sum_{i}^{sumk}b_{i+j} ]

这样a和b就可以逆推出来了
然后dp[0]就等于b[0]/(1-a[0])
然后就好了

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int T,n,k1,k2,k3,ax,bx,cx;
double p[700],P,a[700],b[700];
int main(){
    scanf("%d",&T);
    while(T--){
        memset(p,0,sizeof(p));
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        scanf("%d %d %d %d %d %d %d",&n,&k1,&k2,&k3,&ax,&bx,&cx);
        P=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!=ax||j!=bx||k!=cx)
                        p[i+j+k]+=P;
        for(int i=n;i>=0;i--){
            a[i]=P,b[i]=1;
            for(int k=0;k<=k1+k2+k3;k++)
                a[i]+=a[i+k]*p[k],b[i]+=b[i+k]*p[k];
        }
        double ans=b[0]/(1-a[0]);
        printf("%.15lf
",ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/dreagonm/p/10527521.html