概率dp总结

概率dp总结

  1. 概率一般是正推,期望一般是逆推.
  2. 很多题要推公式,不能上去就dp.
  3. 可以利用矩阵快速幂加速,高斯消元解方程,待定系数法推公式。
  4. 超内存要想到滚动数组。
  5. 形成环要迭代推公式
  6. 树上的问题要讨论叶子节点和非叶子节点的递推公式,用待定系数法求系数,dfs爆搜。
  7. 有关概率的问题,应该要从反面多思考,逆向思考,从不同角度思考。

1.POJ3744
题意:起点是1,每次有p的概率走一步,或者1-p的概率走两步,路径上会有地雷,问你走出去的概率。
思路:××dp[1]=1,dp[i]=dp[i-1] × p+dp[i-2]×(i-p)××;
范围这么大,可以××矩阵快速幂优化递推××,当时路径上点有地雷该怎么办?分段求:1-x[1],x[1]+1-x[2]…
那么安全走完第一段的概率就是××1-p[x1]××
最后的答案累乘就行了。
总结:矩阵快速幂优化,正难则反

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=15;
const int mod=1e9+7;
int a[maxn],vis[maxn];
double dp[maxn];
int n;
double p;
struct mat
{
    double m[2][2];
    mat()
    {
        memset(m,0,sizeof m);
    }
};
mat mul(mat a,mat b)
{
    mat ans;
    for(int i=0; i<=1; i++)
    {
        for(int j=0; j<=1; j++)
        {
            for(int k=0; k<=1; k++)
                ans.m[i][j]=ans.m[i][j]+a.m[i][k]×b.m[k][j];
        }
    }
    return ans;
}
mat qpow(ll b)
{
    mat ans;
    for(int i=0; i<=1; i++)
        ans.m[i][i]=1.0;
    mat base;
    base.m[0][0]=p,base.m[0][1]=1.0-p;
    base.m[1][0]=1.0,base.m[1][1]=0;
    while(b)
    {
        if(b&1)
            ans=mul(ans,base);
        base=mul(base,base);
        b>>=1;
    }
    return ans;
}
int main()
{
    while(~scanf("%d%lf",&n,&p))
    {
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        sort(a+1,a+n+1);
        double ans=1.0;
        a[0]=0;
        for(int i=0; i<n; i++)
            ans=ans×(1.0-qpow(a[i+1]-a[i]-1).m[0][0]);
        printf("%.7f\n",ans);
    }


}

2.POJ2096
题意:一个软件有s个子系统,会产生n种bug
某人一天发现一个bug,这个bug属于一个子系统,属于一个分类,每个bug属于某个子系统的概率是1/s ,属于某种分类的概率是1/n, 问发现n种bug,每个子系统都发现bug的天数的期望。
思路:考虑逆推dp[i][j]表示已经找到i种bug,j个系统的bug,达到目标状态的天数的期望,dp[n][s]=0;要求的答案是dp[0][0];
dp[i][j]可以转化成以下四种状态:
dp[i][j],发现一个bug属于已经有的i个分类和j个系统.
dp[i][j+1],发现一个bug属于已有的分类,不属于已有的系统
dp[i+1][j],发现一个bug属于已有的系统,不属于已有的分类
dp[i+1][j+1],发现一个bug不属于已有的系统,不属于已有的分类
×对应的概率移项就能逆推出dp[0][0];

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=1005;
const int mod=1e9+7;
int n,m;
double dp[maxn][maxn];
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
       dp[n][m]=0;
       for(int i=n;i>=0;i--)
       {
           for(int j=m;j>=0;j--)
           {
               if(i==n&&j==m)continue;
               double p1=(i×j)×1.0/(n×m);
               double p2=((n-i)×j)×1.0/(n×m);
               double p3=((m-j))×1.0/(n×m);
               double p4=((n-i)×(m-j))×1.0/(n×m);
               //cout<<p1<<" "<<p2<<" "<<p3<<" "<<p4<<endl;
               dp[i][j]=(dp[i+1][j]×p2+dp[i][j+1]×p3+dp[i+1][j+1]×p4+1.0)/(1-p1);
           }
       }
       printf("%.4f\n",dp[0][0]);
    }


}

3.ZOJ3329
题意:有三个骰子,分别有k1,k2,k3个面。
每次掷骰子,如果三个面分别为a,b,c则分数置0,否则加上三个骰子的分数之和。
当分数大于n时结束。求游戏的期望步数。初始分数为0。
思路:设dp[i]表示达到i分时到达目标状态的期望,答案就是dp[0];
则dp[i]=∑(pk × dp[i+k])+dp[0] × p0+1;
待定系数法设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]);

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
const int maxn=605;
const int mod=998244353;
int n;
double A[maxn],B[maxn],p[maxn];
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n,k1,k2,k3,a,b,c;
        scanf("%d%d%d%d%d%d%d",&n,&k1,&k2,&k3,&a,&b,&c);
        int ksum=k1+k2+k3;
        double p0=1.0/(k1×k2×k3);
        memset(p,0,sizeof p);
        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)
                        p[i+j+k]+=p0;
                        
                }
            }
        }
        memset(A,0,sizeof A);
        memset(B,0,sizeof B);
        for(int i=n;i>=0;i--)
        {
            for(int j=3;j<=ksum;j++)
            {
                A[i]+=A[i+j]×p[j];
                B[i]+=B[i+j]×p[j];
            }
            A[i]+=p0;
            B[i]+=1;
        }
        printf("%.16f\n",B[0]/(1-A[0]));
    }

}

4.HDU4405
题意:掷色子决定走几步,某些点有传送门(只能传送一次)😚到达终点的期望步数。
思路:dp[i]=dp[i+k]×p+1,逆推求dp[0],传送门直接就是dp[i]=dp[nxt[i]];

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
const int maxn=2e5+5;
const int mod=998244353;
int n,m,nxt[maxn],x,y;
double dp[maxn];
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0)break;
        double p0=1.0/6;
        for(int i=1;i<=n;i++)nxt[i]=i;
        for(int i=1;i<=m;i++)
            scanf("%d%d",&x,&y),nxt[x]=y;
        memset(dp,0,sizeof dp);
        for(int i=n-1;i>=0;i--)
        {
            for(int j=1;j<=6;j++)
            {
                dp[i]+=dp[i+j]×p0;
            }
            dp[i]+=1.0;
            //dp[i]=min(dp[i],dp[nxt[i]]);
            dp[i]=dp[nxt[i]];
        }
        printf("%.4f\n",dp[0]);
    }
}

5.HDU4089
题意:有n个人排队等着在官网上激活游戏。Tomato排在第m个。
对于队列中的第一个人。有一下情况:
1、激活失败,留在队列中等待下一次激活(概率为p1)
2、失去连接,出队列,然后排在队列的最后(概率为p2)
3、激活成功,离开队列(概率为p3)
4、服务器瘫痪,服务器停止激活,所有人都无法激活了。
求服务器瘫痪时Tomato在队列中的位置<=k的概率。
思路:
设dp[i][j]表示i个人排队,Tomato排在第j个位置,达到目标状态的概率(j<=i)
dp[n][m]就是所求

j== 1:    dp[i][1]=p1×dp[i][1]+p2×dp[i][i]+p4;
2<=j<=k: dp[i][j]=p1×dp[i][j]+p2×dp[i][j-1]+p3×dp[i-1][j-1]+p4;
k<j<=i:  dp[i][j]=p1×dp[i][j]+p2×dp[i][j-1]+p3×dp[i-1][j-1];
化简:
j==1:    dp[i][1]=p×dp[i][i]+p41;
2<=j<=k: dp[i][j]=p×dp[i][j-1]+p31×dp[i-1][j-1]+p41;
k<j<=i:  dp[i][j]=p×dp[i][j-1]+p31×dp[i-1][j-1];

其中:
p=p2/(1-p1);
p31=p3/(1-p1)
p41=p4/(1-p1)

可以循环i=1->n 递推求解dp[i].在求解dp[i]的时候dp[i-1]就相当于常数了。
在求解dp[i][1~i]时等到下列i个方程
j==1:   dp[i][1]=p×dp[i][i]+c[1];
2<=j<=k:dp[i][j]=p×dp[i][j-1]+c[j];
k<j=i:  dp[i][j]=p×dp[i][j]+c[j];
其中c[j]都是常数了。上述方程可以解出dp[i]了。
首先是迭代得到 dp[i][i].然后再代入就可以得到所有的dp[i]了。

注意特判一种情况。就是p4<eps时候,就不会崩溃了,应该直接输出0。
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=2005;
double dp[2][maxn];
double p[maxn],c[maxn],p1,p2,p3,p4;
int n,m,k;
int main()
{
    while(~scanf("%d%d%d%lf%lf%lf%lf",&n,&m,&k,&p1,&p2,&p3,&p4))
    {
        if(p3<1e-5)
        {
            printf("%.5f\n",0);
            continue;
        }
        double p21=p2/(1-p1);
        double p41=p4/(1-p1);
        double p31=p3/(1-p1);
        dp[1][1]=p41/(1-p21);
        p[0]=1.0;
        for(int i=1;i<=n;i++)
            p[i]=p[i-1]*p21;
        for(int i=2;i<=n;i++)
        {
            c[1]=p41;
            for(int j=2;j<=k;j++)
              c[j]=p31*dp[(i-1)&1][j-1]+p41;
            for(int j=k+1;j<=n;j++)
              c[j]=p31*dp[(i-1)&1][j-1];

            double sum=c[1]*p[i-1];
            for(int j=2;j<=i;j++)
                sum=sum+c[j]*p[i-j];

            dp[i&1][i]=sum/(1-p[i]);
            dp[i&1][1]=p21*dp[i&1][i]+c[1];

            for(int j=2;j<i;j++)
                dp[i&1][j]=p21*dp[i&1][j-1]+c[j];

        }
        printf("%.5f\n",dp[n&1][m]);
    }
}

6.HDU4035
题意:
有n个房间,由n-1条隧道连通起来,实际上就形成了一棵树,
从结点1出发,开始走,在每个结点i都有3种可能:
1.被杀死,回到结点1处(概率为ki)
2.找到出口,走出迷宫 (概率为ei)
3.和该点相连有m条边,随机走一条
求:走出迷宫所要走的边数的期望值。
设 E[i]表示在结点i处,要走出迷宫所要走的边数的期望。E[1]即为所求。

叶子结点:
E[i] = ki*E[1] + ei*0 + (1-ki-ei)*(E[father[i]] + 1);
     = ki*E[1] + (1-ki-ei)*E[father[i]] + (1-ki-ei);

非叶子结点:(m为与结点相连的边数)
E[i] = ki*E[1] + ei*0 + (1-ki-ei)/m*( E[father[i]]+1 + ∑( E[child[i]]+1 ) );
     = ki*E[1] + (1-ki-ei)/m*E[father[i]] + (1-ki-ei)/m*∑(E[child[i]]) + (1-ki-ei);

设对每个结点:E[i] = Ai*E[1] + Bi*E[father[i]] + Ci;

对于非叶子结点i,设j为i的孩子结点,则
∑(E[child[i]]) = ∑E[j]
               = ∑(Aj*E[1] + Bj*E[father[j]] + Cj)
               = ∑(Aj*E[1] + Bj*E[i] + Cj)
带入上面的式子得
(1 - (1-ki-ei)/m*∑Bj)*E[i] = (ki+(1-ki-ei)/m*∑Aj)*E[1] + (1-ki-ei)/m*E[father[i]] + (1-ki-ei) + (1-ki-ei)/m*∑Cj;
由此可得
Ai =        (ki+(1-ki-ei)/m*∑Aj)   / (1 - (1-ki-ei)/m*∑Bj);
Bi =        (1-ki-ei)/m            / (1 - (1-ki-ei)/m*∑Bj);
Ci = ( (1-ki-ei)+(1-ki-ei)/m*∑Cj ) / (1 - (1-ki-ei)/m*∑Bj);

对于叶子结点
Ai = ki;
Bi = 1 - ki - ei;
Ci = 1 - ki - ei;

从叶子结点开始,直到算出 A1,B1,C1;

E[1] = A1*E[1] + B1*0 + C1;
所以
E[1] = C1 / (1 - A1);
若 A1趋近于1则无解...
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=10005;
const double eps=1e-9;
vector<int>G[maxn];
int n,m;
double k[maxn],e[maxn],A[maxn],B[maxn],C[maxn];
void dfs(int u,int fa)
{
    int m=G[u].size();
    A[u]=k[u];
    B[u]=(1-k[u]-e[u])/m;
    C[u]=(1-k[u]-e[u]);
    double down=0;
    for(auto v:G[u])
    {
        if(v==fa)
            continue;
            dfs(v,u);
//        if(!dfs(v,u))
//            return 0;
        A[u]+=(1-k[u]-e[u])/m*A[v];
        C[u]+=(1-k[u]-e[u])/m*C[v];
        down+=(1-k[u]-e[u])/m*B[v];
    }
//    if(fabs(1-down)<eps)
//        return 0;
    A[u]/=(1-down);
    B[u]/=(1-down);
    C[u]/=(1-down);
//    return 1;
}
int main()
{
    int t,o=1;
    cin>>t;
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1; i<=n; i++)
            G[i].clear();
        for(int i=1; i<n; i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        for(int i=1; i<=n; i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            k[i]=u*1.0/100;
            e[i]=v*1.0/100;
        }
        dfs(1,-1);
        if(fabs(A[1]-1)>eps)
        {
            printf("Case %d: %.10f\n",o++,C[1]/(1-A[1]));
        }
        else
            printf("Case %d: impossible\n",o++);
    }
}

7.HDU3853
题意:迷宫是一个R*C的布局,每个格子中给出停留在原地,往右走一个,往下走一格的概率,起点在(1,1),终点在(R,C),每走一格消耗两点能量,求出最后所需要的能量期望。
思路:dp[i][j]=p1[i][j]*dp[i][j]+p2[i][j]*dp[i][j+1]+p3[i][j]*dp[i+1][j]+2; 化简得到: dp[i][j]=p2[i][j]*dp[i][j+1]/(1-p1[i][j])+p3[i][j]*dp[i+1][j]/(1-p1[i][j])+2/(1-p1[i][j]);
注意一种情况就是p1[i][j]==1的情况。
题目只是保证答案小于1000000.但是有的点可能永远都不可能到达的。
所以这样的点出现p1[i][j]是允许的。
否则就会WA了。

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=1005;
const double eps=1e-9;
vector<int>G[maxn];
int n,m;
double p0[maxn][maxn],p1[maxn][maxn],p2[maxn][maxn];
double dp[maxn][maxn];
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            scanf("%lf%lf%lf",&p0[i][j],&p1[i][j],&p2[i][j]);
        memset(dp,0,sizeof dp);
        for(int i=n;i>=1;i--)
        {
            for(int j=m;j>=1;j--)
            {
                if(i==n&&j==m)continue;
                if(fabs(p0[i][j]-1)<eps)continue;
                dp[i][j]=(dp[i+1][j]*p2[i][j]+dp[i][j+1]*p1[i][j]+2)/(1-p0[i][j]);
            }
        }
        printf("%.3f\n",dp[1][1]);

  }
}

8.POJ 2151
题意:
ACM比赛中,共M道题,T个队,pij表示第i队解出第j题的概率
问 每队至少解出一题且冠军队至少解出N道题的概率。

解析:

设dp[i][j][k]表示第i个队在前j道题中解出k道的概率
则:
dp[i][j][k]=dp[i][j-1][k-1]*p[j][k]+dp[i][j-1][k]*(1-p[j][k]);
先初始化算出dp[i][0][0]和dp[i][j][0];
设s[i][k]表示第i队做出的题小于等于k的概率
则s[i][k]=dp[i][M][0]+dp[i][M][1]+``````+dp[i][M][k];

则每个队至少做出一道题概率为P1=(1-s[1][0])*(1-s[2][0])*(1-s[T][0]);
每个队做出的题数都在1~N-1的概率为P2=(s[1][N-1]-s[1][0])*(s[2][N-1]-s[2][0])*```(s[T][N-1]-s[T][0]);

最后的答案就是P1-P2``
#include<iostream>
#include<cstring>
#include<cstdio>
#include<math.h>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=10005;
const double eps=1e-9;
vector<int>G[maxn];
int n,m,sum;
double dp[1005][40][40];
double dp2[1005][40];
double p[1005][40];
int main()
{
    while(cin>>m>>n>>sum)
    {
        if(n==0&&m==0&&sum==0)
            break;
        memset(dp,0,sizeof dp);
        memset(dp2,0,sizeof dp2);
        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++)
                scanf("%lf",&p[i][j]);
        for(int i=1; i<=n; i++)
        {
            dp[i][0][0]=1.0;
            for(int j=1;j<=m;j++)
                dp[i][j][0]=dp[i][j-1][0]*(1-p[i][j]);
            for(int j=1; j<=m; j++)
            {
                for(int k=1; k<=j; k++)
                    dp[i][j][k]=p[i][j]*dp[i][j-1][k-1]+(1-p[i][j])*dp[i][j-1][k];
            }
            dp2[i][0]=dp[i][m][0];
            for(int k=1;k<=m;k++)
                dp2[i][k]=dp[i][m][k]+dp2[i][k-1];
        }
        double p1=1.0;
        double p2=1.0;
        for(int i=1;i<=n;i++)
            p1*=(1-dp2[i][0]);
        for(int i=1;i<=n;i++)
            p2*=(dp2[i][sum-1]-dp2[i][0]);
        printf("%.3f\n",p1-p2);
    }


}

9.148D

题意:
原来袋子里有w只白鼠和b只黑鼠
龙和王妃轮流从袋子里抓老鼠。谁先抓到白色老师谁就赢。
王妃每次抓一只老鼠,龙每次抓完一只老鼠之后会有一只老鼠跑出来。
每次抓老鼠和跑出来的老鼠都是随机的。
如果两个人都没有抓到白色老鼠则龙赢。王妃先抓。
问王妃赢的概率。

解析:
设dp[i][j]表示现在轮到王妃抓时有i只白鼠,j只黑鼠,王妃赢的概率
明显 dp[0][j]=0,0<=j<=b;因为没有白色老鼠了
      dp[i][0]=1,1<=i<=w;因为都是白色老鼠,抓一次肯定赢了。
      dp[i][j]可以转化成下列四种状态:
      1、王妃抓到一只白鼠,则王妃赢了,概率为i/(i+j);
      2、王妃抓到一只黑鼠,龙抓到一只白色,则王妃输了,概率为j/(i+j)*i/(i+j-1).
      3、王妃抓到一只黑鼠,龙抓到一只黑鼠,跑出来一只黑鼠,则转移到dp[i][j-3]。
      概率为j/(i+j)*(j-1)/(i+j-1)*(j-2)/(i+j-2);
      4、王妃抓到一只黑鼠,龙抓到一只黑鼠,跑出来一只白鼠,则转移到dp[i-1][j-2].
      概率为j/(i+j)*(j-1)/(i+j-1)*i/(i+j-2);
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1005;
const double eps=1e-9;
vector<int>G[maxn];
int w,b,sum;
double dp[maxn][maxn];
int main()
{

    cin>>w>>b;
    for(int i=0; i<=b; i++)
        dp[0][i]=0;
    for(int i=1; i<=w; i++)
        dp[i][0]=1;
    for(ll i=1; i<=w; i++)
    {
        for(ll j=1; j<=b; j++)
        {
            dp[i][j]+=(i)*1.0/(i+j);
            if(j>=2)
                dp[i][j]+=(j*(j-1)*(i))*1.0/((i+j)*(i+j-1)*(i+j-2))*dp[i-1][j-2];
            if(j>=3)
                dp[i][j]+=(j*(j-1)*(j-2))*1.0/((i+j)*(i+j-1)*(i+j-2))*dp[i][j-3];
        }
    }
    printf("%.10f\n",dp[w][b]);
}

10.SGU495

排队取,第1个人取到1个,dp[1]=1;
后面的人dp[i]=p取到礼物盒子+dp取到礼物=(n-dp[i-1])/n + dp[i-1]
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e6+6;
const int mod=998244353;
const double eps=1e-9;
double dp[maxn];
int main()
{
    int n,m;
    while(cin>>n>>m)
    {
        dp[1]=1;
        for(int i=2;i<=m;i++)
        {
            dp[i]=(1-dp[i-1])*dp[i-1]+dp[i-1]*(dp[i-1]-1.0/n);
        }
        double ans=0;
        for(int i=1;i<=m;i++)
            ans=ans+dp[i];
        printf("%.10f\n",ans);
    }
}

11.ZOJ3640

一只吸血鬼,有n条路给他走,每次他随机走一条路,
每条路有个限制,如果当时这个吸血鬼的攻击力大于
等于某个值,那么就会花费t天逃出去,否则,花费1天
的时间,并且攻击力增加,问他逃出去的期望

记忆化搜索:或者递推;
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
const int mod=1e9+7;
const double eps=1e-5;
const double sgn=(sqrt(5.0)+1.0)*1.0/2;
double dp[maxn];int t[maxn];
int a[maxn],n,f;
double dfs(int now)
{
    if(dp[now])
        return dp[now];
    for(int i=1;i<=n;i++)
    {
        if(now>a[i])
            dp[now]+=t[i];
        else
            dp[now]+=(1+dfs(now+a[i]));
    }
    dp[now]/=n;
    return dp[now];
}
int main()
{
    while(cin>>n>>f)
    {
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        sort(a+1,a+n+1);
        for(int i=1; i<=n; i++)
            t[i]=a[i]*a[i]*sgn;
        for(int i=a[n]*2; i>=f; i--)
        {
            dp[i]=0;
            for(int j=1; j<=n; j++)
            {
                if(i<=a[j])
                    dp[i]+=(1+dp[i+a[j]]);
                else
                    dp[i]+=t[j];
            }
            dp[i]/=n;
            dp[i]=0;
        }
        printf("%.3f\n",dfs(f));
    }
}

HDU4436

N(1<=N<=20)张卡片,每包中含有这些卡片的概率为p1,p2,````pN.
每包至多一张卡片,可能没有卡片。
求需要买多少包才能拿到所以的N张卡片,求次数的期望。
1、已经拥有
2、没有拥有
3、没有卡片
状态压缩dp

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+1e5;
const int mod=1e9+7;
const double eps=1e-5;
const double sgn=(sqrt(5.0)+1.0)*1.0/2;
double p[maxn];
double dp[maxn],sum;
int n;
int main()
{
    while(cin>>n)
    {
        sum=0;
        for(int i=0; i<n; i++)
            scanf("%lf",&p[i]),sum+=p[i];
        sum=1-sum;
        ll sta=(1<<n)-1;
        for(int i=sta-1; i>=0; i--)
        {
            double p1=0,p2=1;
            for(int j=0; j<n; j++)
            {
                if(i&(1<<j))
                    p1+=p[j];
                else
                    p2+=dp[i|(1<<j)]*p[j];
            }
            dp[i]=p2/(1-sum-p1);
        }
        printf("%.5f\n",dp[0]);
    }



}

HDU4418

题目:给出一个数轴,有一个起点和一个终点,某个人可以
走123……m步,每一种情况有一个概率,初始有一个方向,
走到头则返回,问到达终点的期望步数为多少。
高斯消元概率dp,dp[i]表示走到该点到达终点还需要的步数的期望。
那么有dp[i]=∑pk/100*(dp[i+k]+k)。
方程式很简单的,但是细节很多,具体见代码。
首先由于一些节点到达不了,需要bfs一遍编号。
有一个巧妙的方向处理就是虚拟出n-2个节点(除起点外其他点都有两个点),这样就不用考虑来回走了,加上对称性,如果开始方向为1,可以将起点sx变为cnt-sx,这样方向就不用处理了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=205;
const int mod=1e9+7;
const double eps=1e-9;
const double sgn=(sqrt(5.0)+1.0)*1.0/2;
int n,d,N,m,st,ed;
double a[maxn][maxn],x[maxn];
double p[maxn];
int num[maxn],equ,var,cnt;
int Gauss()
{
    int i,j,k,col,max_r;
    for(k=0,col=0; k<equ&&col<var; k++,col++)
    {
        max_r=k;
        for(i=k+1; i<equ; i++)
            if(fabs(a[i][col])>fabs(a[max_r][col]))
                max_r=i;
        if(fabs(a[max_r][col])<eps)
            return 0;
        if(k!=max_r)
        {
            for(j=col; j<var; j++)
                swap(a[k][j],a[max_r][j]);
            swap(x[k],x[max_r]);
        }
        x[k]/=a[k][col];
        for(j=col+1; j<var; j++)
            a[k][j]/=a[k][col];
        a[k][col]=1;
        for(i=0; i<equ; i++)
            if(i!=k)
            {
                x[i]-=x[k]*a[i][k];
                for(j=col+1; j<var; j++)
                    a[i][j]-=a[k][j]*a[i][col];
                a[i][col]=0;
            }
    }
    return 1;
}
void bfs(int st)
{
    memset(num,-1,sizeof num);
    queue<int>q;
    q.push(st);cnt=0;
    num[st]=cnt++;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=1; i<=m; i++)
        {
            int v=(i+u)%N;
            if(p[i]<eps)
                continue;
            if(num[v]==-1)
                num[v]=cnt++,q.push(v);
        }
    }
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        scanf("%d%d%d%d%d",&n,&m,&ed,&st,&d);
        for(int i=1; i<=m; i++)
        {
            scanf("%lf",&p[i]);
            p[i]/=100;
        }
        if(st==ed)
        {
            printf("0.00\n");
            continue;
        }
        N=(n-1)*2;
        if(d==1)
            st=N-st;
        bfs(st);
        if(num[ed]==-1&&num[N-ed]==-1)
        {
            printf("Impossible !\n");
            continue;
        }
        equ=var=cnt;
        //cout<<cnt<<"***\n";
        memset(a,0,sizeof a);
        memset(x,0,sizeof x);
        for(int i=0; i<N; i++)
        {
            if(num[i]!=-1)
            {
                if(i==ed||i==N-ed)
                {
                    a[num[i]][num[i]]=1;
                    x[num[i]]=0;
                    continue;
                }
                a[num[i]][num[i]]=1;
                for(int j=1; j<=m; j++)
                {
                    int v=(i+j)%N;
                    if(num[v]==-1)
                        continue;
                    a[num[i]][num[v]]-=p[j];
                    x[num[i]]+=j*p[j];
                }
            }
        }
        if(Gauss())
            printf("%.2f\n",x[num[st]]);
        else
            printf("Impossible !\n");

    }


}

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值