学习笔记——概率期望

P4316 绿豆蛙的归宿

拓扑排序+概率期望

概率期望题一般有两种做题方式:正推,逆推
对于终止状态确定的一般用逆推
对于起始状态确定的一般用正推

  1. 逆推
    这道题中,定义dp数组 \(f_i\) 表示从 \(i\) 到终点的路径长度期望,显然,\(f_n=0\),可以采取逆推的策略,式子如下:

\[f_x=\sum_{以x为起点的边}\frac{f_{y}+cost}{outdeg_x} \]

  1. 正推
    如果定义dp数组 \(f_i\) 表示从1到 \(i\) 的路径长度期望,则 \(f_1=0\),可以进行正推
    然而我不会,逃

P4206 聪聪与可可

奇妙的预处理

因为点数极小,所以可以直接bfs预处理最短路,来预处理聪聪每次往哪里走,然后记忆化搜索每次可可随机走的情况即可

Code

#include<bits/stdc++.h>
#define N 1005
using namespace std;

int n,E,c,m;
int dis[N][N],vis[N][N],step[N][N],v[N][N];
struct edge{
    int v,nxt;
}e[N*2];
int head[N],tot,deg[N];
double dp[N][N];
inline void add(int u,int v);
void bfs(int x);
double dfs(int i,int j);

int main(){
    cin>>n>>E;
    cin>>c>>m;
    for(int i=1;i<=E;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
        deg[u]++,deg[v]++;
    }
    memset(dis,0x7f,sizeof(dis));
    for(int i=1;i<=n;i++) bfs(i);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            int mem=0;
            for(int k=head[i];k;k=e[k].nxt){
                if(dis[j][e[k].v]<dis[j][mem])
                    mem=e[k].v;
                if(dis[j][e[k].v]==dis[j][mem])
                    mem=min(mem,e[k].v);
            }
            step[i][j]=mem;
        }
    printf("%.3lf",dfs(c,m));
}

inline void add(int u,int v){
    e[++tot].v=v;
    e[tot].nxt=head[u];
    head[u]=tot;
}

void bfs(int x){
    queue<int> q;
    q.push(x);
    dis[x][x]=0;
    while(!q.empty()){
        int u=q.front();q.pop();
        vis[x][u]=true;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].v;
            if(!vis[x][v]){
                vis[x][v]=true;
                dis[x][v]=dis[x][u]+1;
                q.push(v);
            }
        }
    }
}

double dfs(int i,int j){
    if(v[i][j]) return dp[i][j];
    v[i][j]=true;
    if(i==j) return dp[i][j]=0;
    int fir=step[i][j],sec=step[fir][j];
    if(fir==j||sec==j) return dp[i][j]=1;
    double sum=0;
    for(int k=head[j];k;k=e[k].nxt)
        sum+=dfs(sec,e[k].v);
    sum+=dfs(sec,j);
    return dp[i][j]=sum/(double)(deg[j]+1)+(double)1;
}

P1654 OSU!

这道题要维护 \(\sum E(x^3)\),但明显 \(E(x^3)\ne E(x)^3\)
逐步考虑 \(E(x),E(x^2)\)
考虑状态转移方程

\[E(x+1)=(E(x)+1)\cdot p_i \\ E((x+1)^2)=E(x^2+2x+1)=(E(x^2)+2E(x)+1)\cdot p_i \\ E((x+1)^3)=(1-p_i)\cdot E(x^3)+(E(x^3)+3E(x^2)+3E(x)+1)\cdot p_i =E(x^3)+(3E(x^2)+3E(x)+1)\cdot p_i \]

维护三个变量转移即可,这里要注意最后一个的期望与前两个的独立

Code
#include<bits/stdc++.h>
#define N 100005
using namespace std;

int n;
double p,x1,x2,x3;

int main(){
    cin>>n;
    for(int i=1;i<=n;++i){
        scanf("%lf",&p);
        x3=x3+(3*x2+3*x1+1)*p;
        x2=(x2+2*x1+1)*p;
        x1=(x1+1)*p;
    }
    printf("%.1lf",x3);
}

Red is good

题面:
桌面上有R张红牌和B张黑牌,随机打乱顺序后放在桌面上,开始一张一张地翻牌,翻到红牌得到1美元,黑牌则付出1美元。可以随时停止翻牌,在最优策略下平均能得到多少钱。

设dp数组 \(f_{i,j}\) 表示有 \(i\) 个红牌 \(j\) 个黑牌时候的最优期望
\(f_{i,j}\) 可以由 \(f_{i-1,j},f_{i,j-1}\) 推出:

\[f_{i,j}=(f_{i-1,j}+1)*\frac{i}{i+j}+(f_{i,j-1}-1)\frac{j}{i+j} \]

意义为最后一张翻的牌为红牌或黑牌对答案的影响

这道题的关键在于“最优策略”是什么,推状态转移方程的途中可以看出来,如果 \(f_{i,j}<0\) 的话,其实在采取最优策略的情况下已经不能够保证正收益了,所以不拿为上,直接令 \(f_{i,j}=0\)

Code
#include<bits/stdc++.h>
#define N 5005
using namespace std;

int r,b;
double dp[N][N];

int main(){
    cin>>r>>b;
    for(int i=0;i<=r;i++)
        for(int j=0;j<=b;j++){
            if(i>0) dp[i][j]+=(double)i/(double)(i+j)*(dp[i-1][j]+1);
            if(j>0) dp[i][j]+=(double)j/(double)(i+j)*(dp[i][j-1]-1);
            if(dp[i][j]<0) dp[i][j]=0;
        }
    printf("%.6lf",dp[r][b]-0.0000005);
}

守卫者的挑战

题面:
有n次挑战,每次挑战获胜可以得到一个地图碎片值为-1 或者 可以得到一个包包用来装地图碎片,最开始有一个包,每个挑战有一个获胜概率,现在让你求至少获胜L轮,挑战完n轮后能用背包装下地图碎片的概率

因为最多挑战 \(n\) 轮,所以最多获得 \(n\) 个碎片 \(n\le 200\),所以包包容量数太大的话完全没有用的,统一取成n即可

设计dp数组 \(f_{i,j,k}\) 为挑战 \(i\) 次,获胜 \(j\) 次,目前包包剩余容量为 \(k\) 的概率(可以为负),这里就出现了数组下标为负的情况,下标整体加上 \(n\) 即可

状态转移方程直接列出来,不解释:

\[f_{i,j,k}=f_{i-1,j,k}*(1-p_i)+f_{i-1,j-1,k-a_i}*p_i \]

Code
#include<bits/stdc++.h>
#define N 205
using namespace std;

int n,l,k,a[N];
double p[N],dp[N][N][N*2],ans;

int main(){
    cin>>n>>l>>k;
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        p[i]=x/100.0;
    }
    for(int i=1;i<=n;i++)
        scanf("%d",a+i);
    k=min(n,k);
    dp[0][0][n+k]=1;
    for(int i=0;i<n;i++)
        for(int j=0;j<=i;j++)
            for(int h=-i;h<=n;h++){
                dp[i+1][j][h+n]+=dp[i][j][h+n]*(1-p[i+1]);
                dp[i+1][j+1][min(h+a[i+1],n)+n]+=dp[i][j][h+n]*p[i+1];
            }
    for(int i=l;i<=n;i++)
        for(int j=n;j<=n*2;j++)
            ans+=dp[n][i][j];
    printf("%.6lf",ans);
}

P1297 单选错位

水题,古典概型

因为权值都为1,所以期望直接当概率算就行

对于 \(i\) 位置的选项迁移到 \(i+1\) 位置,两题选项的情况总数为 \(a_i\cdot a_{i+1}\),而两题答案相同的情况数为 \(\min\{a_i,a_{i+1}\}\),所以做对的概率为 \(\dfrac{\min\{a_i,a_{i+1}\}}{a_i\cdot a_{i+1}}=\dfrac{1}{\max\{a_i,a_{i+1}\}}\),答案累加一下即可

列队春游

题面:

每个小朋友视野距离的期望值根据定义显然有(\(p(i)\) 为视野距离为 \(i\) 的概率):

\[\begin{aligned} E&=\sum^{n}_{i=1}p(i)\cdot i\\ &=\sum^{n}_{i=1}\sum^{n}_{j=i} p(j)\\ &=\sum^{n}_{i=1}p(x\ge i) \end{aligned} \]

这一步是非常妙的(然而做这道题的时候根本没想把定义式展开)
然后将上式展开,设第 \(i\) 个小朋友有 \(k\) 个人能挡住他(即高度不小于他的人数):

\[E=\sum^n_{i=1}\frac{(n-i+1)A^{k}_{n-i}}{A^{k+1}_{n}} \]

对上述式子的解释:挡不住当前小朋友的人的排列显然对答案无影响,只用考虑能挡住当前小朋友的人即可,所以能挡住当前小朋友的人(算上小朋友本身)的排列数就是全部情况,而如果让当前小朋友的视野 \(\ge i\) 的话,能挡住当前小朋友的人就不能在小朋友前方 \(i-1\) 的地方出现,所以他们能在的地方只有 \(n-i\) 处,而小朋友本身又可以在 \(n-(i-1)=n-i+1\) 处位置出现,所以满足当前小朋友的视野 \(\ge i\) 的方案数为 \((n-i+1)A^{k}_{n-i}\)

然后就需要推个式子:

\[\begin{aligned} \sum^n_{i=1}\frac{(n-i+1)A^{k}_{n-i}}{A^{k+1}_{n}}&=\sum^n_{i=1}\frac{(n-i+1)\frac{(n-i)!}{(n-i-k)!}}{\frac{n!}{(n-k-1)!}}\\ &=\frac{(n-k-1)!}{n!}\sum^n_{i=1}\frac{(n-i+1)!}{(n-i-k)!}\\ &=\frac{(n-k-1)!}{n!}(k+1)!\sum^n_{i=1}\frac{(n-i+1)!}{(n-i-k)!(k+1)!}\\ &=\frac{(n-k-1)!}{n!}(k+1)!\sum^n_{i=1}\binom{n-i+1}{k+1}\\ &=\frac{(n-k-1)!}{n!}(k+1)!\sum^n_{i=1}\binom{i}{k+1}\\ &=\frac{(n-k-1)!}{n!}(k+1)!\binom{n+1}{k+2}\\ &=\frac{n+1}{k+2} \end{aligned} \]

对倒数第二行推导的解释:

\[\sum\limits^n_{i=0}\binom{i}{k}=\binom{n+1}{k+1} \]

然后就可以 \(O(n)\) 直接做了

矩形粉刷

题面:
为了庆祝新的一年到来,小M决定要粉刷一个大木板。大木板实际上是一个W*H的方阵。小M得到了一个神奇的工具,这个工具只需要指定方阵中两个格子,就可以把这两格子为对角的,平行于木板边界的一个子矩形全部刷好。小M乐坏了,于是开始胡乱地使用这个工具。
假设小M每次选的两个格子都是完全随机的(方阵中每个格子被选中的概率是相等的),而且小M使用了K次工具,求木板上被小M粉刷过的格子个数的期望值是多少。

因为期望的权值为1,所以答案可以转化为每个格子被粉刷过(即被矩形包含)的概率之和,但是这样统计明显不可做(因为要考虑重复粉刷的情况),正难则反,统计每个格子不被粉刷的概率再用1减去即可

不难发现,如果选取的这两个点都在当前格子的上边/下边/左边/右边时就不会粉刷到当前格子,对于两个选取的点都在以当前格子为中心的四个角里的话,会重复计算,减去即可

P2059 卡牌游戏

这道题可以用记忆化搜索水过去:

dp数组 \(f_{i,j,k}\) 的定义为当进行到第 \(i\) 轮时轮到了第 \(j\) 个人且当前庄家为 \(k\) 的情况下第 \(j\) 个人的胜率

如果进行到最后一轮只剩下他自己的时候,胜率显然为1,以此为终止条件逐步模拟选牌过程转移即可

Code
#include<bits/stdc++.h>
#define N 55
#define db double
using namespace std;

int n,m,dat[N];
db dp[N][N][N],tmp;//还剩i个人时第j人庄家为k的胜率
db dfs(int i,int j,int k);

int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++)
        scanf("%d",dat+i);
    dp[1][0][0]=1.0;
    for(int i=1;i<=n;i++)
        printf("%.2lf%% ",dfs(n,i-1,0)*100);
}
//g++ game.cpp -o test -std=c++14 -O2 -lm
db dfs(int i,int j,int k){
    if(dp[i][j][k]) return dp[i][j][k];
    if(i==1) return 0.0;
    for(int l=1;l<=m;l++){
        int pos=(k+dat[l]-1)%i;
        if(pos==j) continue;
        if(pos<j) dp[i][j][k]+=dfs(i-1,j-1,pos%(i-1))/(db)m;
        if(pos>j) dp[i][j][k]+=dfs(i-1,j,pos%(i-1))/(db)m;
    }
    return dp[i][j][k];
}

其实还有一种直接dp的做法,其实道理是一样的,只是减去了庄家是谁的一维,而默认庄家为第一个人,做法不再赘述

P1850 换教室

想到最后才发现是道线性dp,然后切了

首先点数十分的小,所以可以直接Floyd求全源最短路

dp数组 \(f_{i,j,0/1}\) 的定义为已经决定了前 \(i\) 个的申请情况,已经申请了 \(j\) 个,第 \(i\) 个申请了 / 没申请 的期望路程

然后分类讨论:

  • 对于第三维为0的情况
  1. 如果前一个未申请,则对答案的影响为 \(dis_{c_{i},c_{i-1}}\)

  2. 如果前一个申请了,则有两种情况:

    • 前一个申请上了 对答案的影响为 $ dis_{d_{i-1},c_i} $,再乘上对应的概率 \(p_{i-1}\)
    • 前一个没申请上 对答案的影响为 \(dis_{c_{i},c_{i-1}}\),概率为 \(1-p_{i-1}\)
  • 对于第三维为1的情况讨论有点多,与上面相似,不再赘述
Code
#include<bits/stdc++.h>
#define N 2005
#define V 305
#define db double
using namespace std;

int n,m,v,e;
int c[N],d[N];
db p[N],dp[N][N][2],mat[V][V];

int main(){
    cin>>n>>m>>v>>e;
    for(int i=1;i<=n;i++)
        scanf("%d",c+i);
    for(int i=1;i<=n;i++)
        scanf("%d",d+i);
    for(int i=1;i<=n;i++)
        scanf("%lf",p+i);
    memset(mat,0x7f,sizeof(mat));
    for(int i=1;i<=e;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        mat[u][v]=min(mat[u][v],(db)w);
        mat[v][u]=min(mat[v][u],(db)w);
    }
    for(int i=1;i<=v;i++)
        mat[i][i]=0;
    for(int k=1;k<=v;k++)
        for(int i=1;i<=v;i++)
            for(int j=1;j<=v;j++)
                mat[i][j]=min(mat[i][j],mat[i][k]+mat[k][j]);
    memset(dp,0x7f,sizeof(dp));
    dp[1][0][0]=0;
    dp[1][1][1]=0;
    for(int i=2;i<=n;i++)
        for(int j=0;j<=m;j++){
            dp[i][j][0]=min({dp[i][j][0],dp[i-1][j][0]+mat[c[i]][c[i-1]],dp[i-1][j][1]+p[i-1]*mat[c[i]][d[i-1]]+(1.0-p[i-1])*mat[c[i]][c[i-1]]});
            if(j) dp[i][j][1]=min({dp[i][j][1],dp[i-1][j-1][0]+p[i]*mat[c[i-1]][d[i]]+(1.0-p[i])*mat[c[i-1]][c[i]],dp[i-1][j-1][1]+p[i]*p[i-1]*mat[d[i-1]][d[i]]+p[i-1]*(1.0-p[i])*mat[d[i-1]][c[i]]+p[i]*(1.0-p[i-1])*mat[c[i-1]][d[i]]+(1.0-p[i])*(1.0-p[i-1])*mat[c[i-1]][c[i]]});
        }
    db ans=0x3f3f3f3f3f3f3f;
    for(int i=0;i<=m;i++)
        ans=min(ans,min(dp[n][i][1],dp[n][i][0]));
    printf("%.2lf",ans);
}
//g++ class.cpp -o test -std=c++14 -O2 -lm

P2473 奖励关

当时确实没有想到可以逆推(原因是找不到合适的dp数组定义)

一看数据范围显然是状压,顺推显然会导致一些考虑重复和后效性的问题,所以逆推

dp数组 \(f_{i,j}\) 定义为从第 \(i\) 轮选到第 \(k\) 轮的最优期望,且前 \(i-1\) 轮选的宝物集合为 \(j\),这样可以不用考虑多种情况,\(f_{1,0}\) 即为答案

这显然倒着转移,有两种情况:

  1. 当前的 \(j\) 能够满足当前宝物的前置条件,可以选择选不选该宝物
  2. 如果满足不了只能继承前面的结果

这样处理就不会有考虑当前该不该选此宝物的问题,顺推需要考虑选当前宝物对之后的影响,逆推则是先选了此宝物再看后面怎么选来满足此条件,在处理这种期望问题的时候,逆推显然是最好的选择,因为不满足情况的转移在之后的转移中会自动舍弃

Code
#include<bits/stdc++.h>
#define N 16
#define db double
using namespace std;

int k,n;
int v[N],tot[N],num;
double dp[105][(1<<N)],ans;

int main(){
    cin>>k>>n;
    int range=(1<<n)-1;
    for(int i=0;i<n;i++){
        int tmp;
        scanf("%d%d",v+i,&tmp);
        while(tmp){
            tot[i]=tot[i]+(1<<(tmp-1));
            scanf("%d",&tmp);
        }
    }
    for(int i=k;i>=1;i--)
        for(int j=0;j<=range;j++){
            for(int l=0;l<n;l++){
                if((tot[l]&j)==tot[l])
                    dp[i][j]+=max(dp[i+1][j],dp[i+1][j|(1<<l)]+v[l]);
                else dp[i][j]+=dp[i+1][j];
            }
            dp[i][j]/=(db)n;
        }
    printf("%.6lf\n",dp[1][0]);
}
//g++ award.cpp -o test -std=c++14 -O2 -lm
posted @ 2022-09-24 21:24  Rolling_star  阅读(154)  评论(1编辑  收藏  举报