[kuangbin带你飞]专题二十一 概率&期望

[kuangbin带你飞]专题二十一 概率&期望

A.dp不行就推公式

 在n个门前选择一扇门出去,
 然后如果第i扇门的 Xi值是正的话,你会花费Xi时间后出去 , 
 如果Xi是负数的话你会花费-Xi时间后回到老地方,并且忘记了刚才的选择, 选择一扇门的概率是等概的。
 求出去的期望。
 
设出去的期望是Y,
那么可以写出一个式子 :Y = P1 * T1 + P2 * (T2 + Y), 
这样的话问题就得到了解决, 最后整理下式子就是 : 
Y = 正数个数的倒数 *abs(Xi)#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+5;
const int eps=1e-5;
typedef long long ll;
typedef pair<ll,ll>p;
ll n,a[maxn];
int main()
{
    
    
    ll t,cas=0;
    cin>>t;
    while(t--)
    {
    
    
        ll sum=0,cnt=0;
        cin>>n;
        for(ll i=1; i<=n; i++)
        {
    
    
            scanf("%lld",&a[i]);
            sum+=abs(a[i]);
            if(a[i]<0)cnt++;
        }
        if(cnt==n)
        {
    
    
            printf("Case %lld: inf\n",++cas);
        }
        else
        {
    
    
            ll gcd=__gcd(sum,n-cnt);
            printf("Case %lld: %lld/%lld\n",++cas,sum/gcd,(n-cnt)/gcd);
        }
    }
}

B.注意边界从后转移

有一排洞穴,你在第一个洞穴,可以获得该洞穴的黄金,然后掷标有1-6的骰子,
是几就往下走几步,并得到该洞穴的黄金。
当离终点小于6步且不合法时就重掷直到合法为止。
求起点出发的黄金的期望。

直接dp向后转移
dp[i]=sigma{
    
    dp[i+k]*p0+a[i+k]}

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e2+5;
const int eps=1e-5;
typedef long long ll;
typedef pair<ll,ll>p;
int n,a[maxn];
double dp[maxn];
int main()
{
    
    
    int t,cas=0;
    cin>>t;
    while(t--)
    {
    
    
        cin>>n;
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        memset(dp,0,sizeof dp);
        dp[n]=0;
        for(int i=n-1; i>=0; i--)
        {
    
    
            if(i+6>n)
            {
    
    
                int down=n-i;
                for(int j=i+1; j<=n; j++)
                {
    
    
                    dp[i]+=(dp[j]+a[j]);
                }
                dp[i]=dp[i]/down;
            }
            else
            {
    
    
                for(int j=i+1; j<=i+6; j++)
                {
    
    
                    dp[i]+=(dp[j]+a[j]);
                }
                dp[i]/=6;
            }
        }
        printf("Case %d: %.7f\n",++cas,dp[1]+a[1]);



    }
}

C.很经典的从后向前转移,注意double
在这里插入图片描述

给定一个整数n,每次操作可以对当前数进行除以它的某个因子。
除以哪个因子是随机的,
求把n变成1的期望步数。

预处理答案:


#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int eps=1e-5;
typedef long long ll;
typedef pair<ll,ll>p;
int n,a[maxn];
double dp[maxn];
int cal(int n)
{
    
    
    int cnt=0;
    for(int i=2; i*i<=n; i++)
    {
    
    
        if(n%i==0)
        {
    
    
            if(i*i!=n)
                cnt+=2;
            else
                cnt+=1;
        }
    }
    cnt+=2;
    return cnt;
}
void init()
{
    
    
    //dp[2]=2;
    for(int i=2; i<=maxn-1; i++)
    {
    
    
        int cnt=cal(i),n=i;
        for(int j=2; j*j<=n; j++)
        {
    
    
            if(n%j==0)
            {
    
    
                dp[i]+=dp[j]/(cnt-1);
                if(j*j!=n)
                    dp[i]+=dp[n/j]/(cnt-1);
            }
        }
        dp[i]+=(cnt)*1.0/(cnt-1);
    }
}
int main()
{
    
    
    init();
    int t,cas=0;
    cin>>t;
    while(t--)
    {
    
    
        cin>>n;
        printf("Case %d: %.7f\n",++cas,dp[n]);
    }
}

E.标程答案肯定出锅,我用java写的大数跟AC代码有误差
应该是精度问题。

在某个星球上一年有 n 天,现在要邀请人参加聚会,
现在使得聚会上至少两个人同一天生日的概率最小为 0.5,
问最少有多少人?

我们用容斥原理,先求所有人生日都不同的概率,用1减去它即可。
m个人每人选一个当做生日:A(m,n)
m个人总共可能有的生日的数:n^m
那么题目就是求:1 - A(m,n)/ n^m >= 0.5

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const double eps=1e-5;
int main()
{
    
    
    int t,cas=0,n;
    cin>>t;
    while(t--)
    {
    
    
        cin>>n;
        int up=1,down=1,st=n,ans=0;double ok=1;
        for(int i=1; i<=400; i++)
        {
    
    
            ok=ok*st*1.0/n;
            st--;
            if(ok<=0.5)
            {
    
    
                ans=i;
                break;
            }
        }
        printf("Case %d: %d\n",++cas,ans-1);

    }

}

E.很经典的背包概率dp
有少于100个银行,每个银行有少于100元的钱,和被抓住的概率,要求你背抓住的概率小于等于v,你最多能获得多少钱.
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const double eps=1e-5;
struct node
{
    
    
    double p;
    int v;
} a[maxn];
double p;
int n;
double dp[maxn];
int main()
{
    
    
    int t,cas=1;
    cin>>t;
    while(t--)
    {
    
    
        memset(dp,0,sizeof dp);
        scanf("%lf%d",&p,&n);
        for(int i=1; i<=n; i++)
            scanf("%d%lf",&a[i].v,&a[i].p);
        dp[0]=1;
        for(int i=1;i<=n;i++)
        {
    
    
            for(int j=maxn;j>=a[i].v;j--)
            {
    
    
                dp[j]=max(dp[j-a[i].v]*(1-a[i].p),dp[j]);
            }
        }
        int ans=0;
        for(int i=0;i<maxn;i++)
        {
    
    
            if(dp[i]>=1-p)
                ans=i;
        }
        printf("Case %d: %d\n",cas++,ans);
    }

}

F.经典高斯消元可前可后解方程

100个格子,从1开始走,每次抛骰子走1~6,若抛出的点数导致走出了100以外,则重新抛一次。有n个格子会单向传送到其他格子,tp[i]表示从i传送到tp[i]1100不会有传送,一个格子也不会有两种传送。问走到100的期望值。

dp[i]表示从格子i走出去的期望次数
分两种情况考虑
格子不可以传送 dp[i]=6⋅∑kj=dp[i+j]+6⋅∑6j=k+1dp[i]+1(i+k=100)
格子可以传送 dp[i]=dp[nxt[i]]
化简得
格子不可以传送 k⋅dp[i]−∑kj=1dp[i+j]=6
格子可以传送 dp[i]=dp[nxt[i]]
由于传送是无序的,所以无法使用递推来解决,只能列矩阵方程来高斯消元

#include<bits/stdc++.h>
using namespace std;
const int maxn=110;
const double eps=1e-10;
double dp[maxn];
int nxt[maxn];
double a[maxn][maxn],x[maxn];//方程的左边的矩阵和等式右边的值,求解之后x存的就是结果
int equ,var;//方程数和未知数个数
int Gauss()
{
    
    
    int i,j,k,col,max_r;
    for(k=1,col=1; 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;
}
int main()
{
    
    
    int t,cas=0;
    cin>>t;
    while(t--)
    {
    
    
        memset(a,0,sizeof a);
        memset(x,0,sizeof x);
        memset(dp,0,sizeof dp);
        for(int i=1; i<maxn; i++)
            nxt[i]=i;
        equ=var=100;
        int m;
        cin>>m;
        for(int i=1; i<=m; i++)
        {
    
    
            int u,v;
            scanf("%d%d",&u,&v);
            nxt[u]=v;
        }
        a[100][100]=1;
        for(int i=99; i>=1; i--)
        {
    
    
            int cnt=min(100-i,6);
            if(nxt[i]!=i)
            {
    
    
                a[i][i]=1;
                a[i][nxt[i]]=-1;
                continue;
            }
            a[i][i]=cnt*1.0/6,x[i]=1;
            for(int j=1; j<=cnt; j++)
            {
    
    
                a[i][i+j]=-1.0/6;
            }

        }
        Gauss();
        printf("Case %d: %.10f\n",++cas,x[1]);



    }





}

G.几何分布 E=1/p;

#include<bits/stdc++.h>
#include<tr1/unordered_map>
const int maxn=1e6+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
typedef long long ll;
using namespace std;
int t,n,cse=0;
int main()
{
    
    
    cin>>t;
    while(t--)
    {
    
    
        scanf("%d",&n);
        double ans=0;
        for(int i=1; i<=n; i++)
            ans=ans+n*1.0/i;
        printf("Case %d: %.10f\n",++cse,ans);
    }

}

H.组合数+DP

大概题意是:岛内有n只老虎和m只鹿,
每天都有两只动物呆在一起,
问最后跑出岛的概率是多少。
(1)你和老虎,老虎吃掉你
(2)老虎和鹿,老虎吃掉鹿
(3)两只鹿,两只都是安全
(4)你和鹿呆,你可以选择杀或者不杀鹿
(5)两只老虎,两只都会死掉
double  dp[maxn][maxn]; // i老虎,j只鹿逃跑的概率 

dp[i][j]:
 
人和鹿呆在一起,发生的概率为
C(1,j) / C(2,1 + i + j) * (dp[i][j] + dp[i][j - 1]) / 2
两只鹿呆在一起,发生的概率为
C(2,J) / C(2,1 + i + j) * dp[i][j]
虎和鹿呆在一起,发生的概率为
i * j / C(2,i + j + 1) * dp[i][j - 1] 鹿去掉 1
两只虎呆在一起,发生的概率为
C(2,i) / C(2,1 + i + j) * dp[i - 2][j] 老虎去掉 2 


#include<bits/stdc++.h>
#include<tr1/unordered_map>
const int maxn=1e3+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
typedef long long ll;
using namespace std;
double dp[maxn][maxn];
int T,t,d,cas=0,ok;
int c2(int n)
{
    
    
    if(n==0||n==1)
        return 0;
    return n*(n-1)/2;
}
int main()
{
    
    
    cin>>T;
    while(T--)
    {
    
    
        scanf("%d%d",&t,&d);
        memset(dp,0,sizeof dp);
        for(int i=0; i<=d; i++)
            dp[0][i]=1;
        for(int i=0; i<=t; i++)
        {
    
    
            for(int j=0; j<=d; j++)
            {
    
    
                if(i==0)
                    continue;
                double p=c2(i+j+1);
                if(j>=1)
                    dp[i][j]+=dp[i][j-1]*i*j*1.0/p;
                if(j>=1)
                    dp[i][j]+=dp[i][j-1]*j*0.5/p;
                if(i>=2)
                    dp[i][j]+=dp[i-2][j]*c2(i)*1.0/p;
                double down=1;
                down-=c2(j)*1.0/p;
                down-=j*0.5/p;
                dp[i][j]/=down;
            }
        }
        printf("Case %d: %.10f\n",++cas,dp[t][d]);
    }
}

I.滚动数组01背包dp

期望dp,用逆推。dp[i][j][k]表示当前状态为第i行,已经输出了j个YES,这一行要输出YES(0)/NO(1)
因此可以得出,
①如果第i要输出YES,那么i+1行如果输出NO,
则i的期望值为i+1的期望值加上+1再乘以输出NO的概率;
i+1行如果输出YES,
则i的期望值为i+1的期望值乘以输出YES的概率。
②如果第i行要输出NO,做法和①相同
递推关系式为
dp[i][j][0]=dp[i+1][j+1][0]*p1+(dp[i+1][j][1]+1)*p2
dp[i][j][1]=(dp[i+1][j+1][0]+1*p1)*p1+dp[i+1][j][1]*p2
因为不能开5000*5000*2的数组,想到第i位只与第i+1位有关,第一维可以滚动。


#include<bits/stdc++.h>
#include<tr1/unordered_map>
const int maxn=5e3+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
typedef long long ll;
using namespace std;
double dp[2][maxn][2];
int n,s,cas=0;
int main()
{
    
    
    int T;
    cin>>T;
    while(T--)
    {
    
    
        scanf("%d%d",&n,&s);
        int yes=s-2*n,no=3*n-s;
        memset(dp,0,sizeof dp);
        for(int i=n-1;i>=0;i--)
        {
    
    
            int ma=min(i,yes),mi=max(0,i-no);
            for(int j=ma;j>=mi;j--)
            {
    
    
                double p1=(yes-j)*1.0/(n-i),p2=(no-(i-j))*1.0/(n-i);
                dp[i%2][j][0]=p1*(dp[(i+1)%2][j+1][0])+p2*(1+dp[(i+1)%2][j][1]);
                dp[i%2][j][1]=p2*(dp[(i+1)%2][j][1])+p1*(1+dp[(i+1)%2][j+1][0]);
            }
        }
        printf("Case %d: %.10f\n",++cas,dp[0][0][0]);
    }
}

J.很神奇的算贡献利用二项式定理

题意:
在一个三维的空间,每个点都有一盏灯,开始全是关的,
现在每次随机选两个点,把两个点之间的全部点,开关都按一遍;问k次过后开着的灯的期望数量;
题解:
肯定不能从随机抽取两个数这里入手的,要求开着的灯的数量就从对每一盏灯,操作结束后灯开着的概率,然后将这些概率求和就是对于整个矩阵到最后开着的灯的数量了,这就把矩阵的问题落实到了对于求每个坐标的概率的问题。
对每个点单独计算贡献,即k次过后每个点开关被按了奇数次的期望
一个点如果被包到所选空间里,那么说明选的两个点,x坐标在这个点两侧,y坐标在这个点两侧,z坐标在这个点两侧;
对于一维的,可以用1-(x-1)*(x-1)+(n-x)*(n-x)(n*n)
求出两个点在x这一点两侧的概率。然后三维的只要当成三个一维的乘起来就行了。

在这里插入图片描述

#include<bits/stdc++.h>
#include<tr1/unordered_map>
const int maxn=5e3+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
typedef long long ll;
using namespace std;
double dp[2][maxn][2];
int n,turn,x,y,z,cas=0;
double qpow(double a,int b)
{
    
    
    double ans=1;
    while(b)
    {
    
    
        if(b&1)
            ans=ans*a;
        a=a*a;
        b>>=1;
    }
    return ans;
}
double cal(int num,int limit)
{
    
    
    return 1-((num-1)*(num-1)+(limit-num)*(limit-num))*1.0/(limit*limit);
}
int main()
{
    
    

    int T;
    cin>>T;
    while(T--)
    {
    
    
        double ans=0;
        scanf("%d%d%d%d",&x,&y,&z,&turn);
        for(int i=1; i<=x; i++)
        {
    
    
            for(int j=1; j<=y; j++)
            {
    
    
                for(int k=1; k<=z; k++)
                {
    
    
                    double p=cal(i,x)*cal(j,y)*cal(k,z);
                    ans=ans+(1-qpow(1-2.0*p,turn))*0.5;
                }
            }
        }
        printf("Case %d: %.10f\n",++cas,ans);
    }
}

K.dfs状压dp 还是挺猛的
double 型别memset

有n个街口和m条街道, 你后边跟着警察,需要进行逃亡,在每个街口你都有≥1个选择:
1)停留在原地5分钟。
2)如果这个街口可以到xi这个街口, 并且, 通过xi可以遍历完所有未走过的街口,那么就加入选择。
每个选择都是等概率的,求警察抓住你所用时间的期望, 即你无路可走时的时间期望。

 

题解:

1. 对于每个点, 先找出所有的选择, 假设有k个选择。

2. 那么E[u] 表示在街口u出发后走完剩余点的时间期望。

则:E[u] = 1 / k * sigma (E[v] + time[u][v]) + 1 / k * (E[u] + 5)。

化简得:E[u] = (sigma (E[v] + time[u][v]) + 5) / (k - 1)

3.[S][u]表示从u点出发, 已完成状态为S,之后走完剩余点所用时间的期望。
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
const int maxn=16;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
typedef long long ll;
typedef pair<int,int> p;
int n,m,cas=0,sta;
vector<p>G[maxn];
double dp[maxn][1<<maxn];
int dfs(int u,int S)
{
    
    
    if(dp[u][S]!=-1)
        return dp[u][S]>-1;
    if(S==sta)
        return dp[u][S]=0,1;
    int cnt=0;
    double up=0;
    for(auto it:G[u])
    {
    
    
        int v=it.first,w=it.second;
        if(((S>>v)&1)==1)
            continue;
        if(dfs(v,S+(1<<v)))
        {
    
    
            cnt++;
            up+=(w+dp[v][S+(1<<v)]);
        }
    }
    if(!cnt)
        return dp[u][S]=-2,0;
    else
        return dp[u][S]=(up+5)*1.0/(cnt),1;
}
int main()
{
    
    
    int T;
    cin>>T;
    while(T--)
    {
    
    
        scanf("%d%d",&n,&m);
        for(int i=0; i<n; i++)
            G[i].clear();
        for(int i=1; i<=m; i++)
        {
    
    
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            G[u].push_back({
    
    v,w});
            G[v].push_back({
    
    u,w});
        }
        sta=(1<<n)-1;
        for(int i=0;i<=n;i++)
        {
    
    
            for(int j=0;j<=sta;j++)
                dp[i][j]=-1;
        }
        dfs(0,1);
        printf("Case %d: %.10f\n",++cas,max(dp[0][1],0.0));

    }
}

N.几何分布的一个拓展

地上有n种棍子,
其中有两种类型, 一种类型是可识别, 一种类型是不可识别,
每个棍子都有一个权值。
当你捡到可识别的, 那么你以后就不会再捡这个棍子, 
如果是不可识别的, 那么你有可能还会捡。
问将所有棍子收集完的权值的期望。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+100;
const int mod=998244353;
typedef long long ll;
int a[maxn],b[maxn],n,cas=0;
int main()
{
    
    
    ll T;
    cin>>T;
    while(T--)
    {
    
    
        double sum1=0,sum2=0;
        cin>>n;
        for(int i=1; i<=n; i++)
        {
    
    
            scanf("%d%d",&a[i],&b[i]);
            if(b[i]==1)
                sum1+=a[i];
            if(b[i]==2)
                sum2+=a[i];
        }
        for(int i=1;i<=n;i++)
            sum1=sum1+sum2/i;
        printf("Case %d: %.10f\n",++cas,sum1);
        //printf("%.10f\n",sum1);





    }


}

O。记忆化搜索:

给你一副扑克牌,54张,
让你求出取出黑梅红方最少分别为a,b,c,d张 的取的次数的期望。
其中大小王可以当做四种牌的任意一种。

题解:dp[a][b][c][d][p][q]表示每种牌分别有a,b,c,d张,
Joker的状态分别作为p和q出现时的期望值
然后记忆化搜索。
#include<bits/stdc++.h>
#include<tr1/unordered_map>
const int maxn=1e3+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
typedef long long ll;
using namespace std;
double dp[15][15][15][15][5][5];
int A,B,C,D,cas=0;
void init()
{
    
    
    for(int i=0; i<=14; i++)
        for(int j=0; j<=14; j++)
            for(int k=0; k<=14; k++)
                for(int a=0; a<=14; a++)
                    for(int b=0; b<=4; b++)
                        for(int v=0; v<=4; v++)
                            dp[i][j][k][a][b][v]=-1;
}
bool judge(int a,int b,int c,int d,int e,int f)
{
    
    
    if(e==1)
        a++;
    if(e==2)
        b++;
    if(e==3)
        c++;
    if(e==4)
        d++;
    if(f==1)
        a++;
    if(f==2)
        b++;
    if(f==3)
        c++;
    if(f==4)
        d++;
    if(a>=A&&b>=B&&c>=C&&d>=D)
        return 1;
    return 0;
}
double dfs(int a,int b,int c,int d,int e,int f)
{
    
    
    if(dp[a][b][c][d][e][f]>eps)
        return dp[a][b][c][d][e][f];
    if(judge(a,b,c,d,e,f))
        return dp[a][b][c][d][e][f]=0;
    double sum=a+b+c+d+(e!=0)+(f!=0);
    if(sum==54)
        return dp[a][b][c][d][e][f]=0;
    double ans=0;
    if(e==0)
    {
    
    
        double mi=1e5+5;
        for(int i=1; i<=4; i++)
            mi=min(mi,dfs(a,b,c,d,i,f));
        ans+=mi;
    }
    if(f==0)
    {
    
    
        double mi=1e5+5;
        for(int i=1; i<=4; i++)
            mi=min(mi,dfs(a,b,c,d,e,i));
        ans+=mi;
    }
    if(a<13)
        ans=ans+(13-a)*dfs(a+1,b,c,d,e,f);
    if(b<13)
        ans=ans+(13-b)*dfs(a,b+1,c,d,e,f);
    if(c<13)
        ans=ans+(13-c)*dfs(a,b,c+1,d,e,f);
    if(d<13)
        ans=ans+(13-d)*dfs(a,b,c,d+1,e,f);
    ans=ans/(54-sum)+1;
    return dp[a][b][c][d][e][f]=ans;
}


int main()
{
    
    
    int T;
    cin>>T;
    while(T--)
    {
    
    
        int cnt=0;
        init();
        scanf("%d%d%d%d",&A,&B,&C,&D);
        if(A>13)
            cnt+=A-13;
        if(B>13)
            cnt+=B-13;
        if(C>13)
            cnt+=C-13;
        if(D>13)
            cnt+=D-13;
        if(cnt>2)
            printf("Case %d: %.10f\n",++cas,-1.0);
        else
            printf("Case %d: %.10f\n",++cas,dfs(0,0,0,0,0,0));
    }
}

猜你喜欢

转载自blog.csdn.net/qq_43653111/article/details/104805978
今日推荐