概率期望简单杂题

前言

云剪切板 link

cnblogs 我相信你!所以我把所有博客题解链都展开了!

有删改


ああウー…… 转载请注明出处 .


概率期望小记

为了省空间,代码压缩了(用的 Mivik 的代码压行机),想看可以自己格式化一下 .

缺省源:头文件 14,15 题缺省高斯消元)

映射表

题号 A B C D E F G H I J K L M N O P Q R
迫真题号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
题目名 绿豆蛙的归宿 聪聪和可可 OSU! Red is good 守卫者的挑战 Easy 单选错位 列队春游 矩形粉刷 卡牌游戏 换教室 奖励关 概率充电器 游走 XOR和路径 分手是祝愿 硬币游戏
是否用到 dp(Y/N) Y Y Y Y Y Y Y N N Y Y Y Y Y Y Y Y N

Upd. S, T 题(chemistry,抽卡)没了,只剩 18 道题了


1. 绿豆蛙的归宿

题面

链接


一张 n 个点的带权 DAG 上随机游走,问 1 走到 n 走过的路径边权和期望 .

简要题解

简单题,令 dpu 为从 1n 的版边和期望,则由期望定义知

dpu=1deguvu(dpv+val(u,v))

其中 deguu 的入度 .

转移顺序是倒着的拓扑序 .

代码

using namespace std;const int N=1e5+500;typedef long long ll;typedef double db;int n,m,deg[N],ii[N];bool vis[N];db dp[N];vector<pair<int,int>>g[N];inline void addedge(int u,int v,int w){g[u].emplace_back(make_pair(v,w));++deg[v];++ii[v];}void koishi(int s){queue<int>q;q.push(s);dp[s]=0;while(!q.empty()){int u=q.front();q.pop();for(auto e:g[u]){int v=e.first,w=e.second;dp[v]+=(dp[u]+w)/deg[v];if(!--ii[v])q.push(v);}}}int main(){scanf("%d%d",&n,&m);for(int i=0,u,v,w;i<m;i++)scanf("%d%d%d",&u,&v,&w),addedge(v,u,w);koishi(n);printf("%.2f",dp[1]);return 0;}

2. 聪聪和可可

题面

链接


一张 n 个点 m 条边的无向图,俩小可爱 A,B 在图上走,初始 A 在点 mB 在点 c,每个时刻 A,B 的走法如下:

  • A 在图上随机游走(A 可能原地不动)
  • B 的策略是每次选一个最接近 A 的出点,如果没到 A 就再走一步 .

每个时刻 A 先走,B 后走,问期望走几个时刻 A,B 相遇 .

简要题解

第二题就放毒瘤,呜呜

首先这个 B 的迷の走位可以预处理:令 to(i,j) 表示 BiAjB 应该走到哪 .

dpi,j 表示 BiAj 的方案数,则

  • i=j,则 dpi,j=0 .
  • i1,2 步就到 A 了,则 dpi,j=1 .
  • 若不然,则:

dpi,j=1+1degjjk or j=kdptr(i,k),k

其中 tr(i,k)=to(to(i,k),k),即 i 走两步 .

照着方程跑 dp,做完了!

代码

using namespace std;const int N=1145;typedef long long ll;typedef double db;int n,m,s,t,dis[N][N],deg[N],to[N][N];db dp[N][N];bool vis[N],viss[N][N];vector<int>g[N];inline void addedge(int u,int v){g[u].emplace_back(v);++deg[u];}void bfs(int s){memset(vis,0,sizeof vis);queue<int>q;q.push(s);dis[s][s]=0;while(!q.empty()){int u=q.front();q.pop();if(vis[u])continue;vis[u]=true;for(auto v:g[u]){if(vis[v])continue;q.push(v);dis[s][v]=min(dis[s][v],dis[s][u]+1);}}}db dfs(int u,int v){if(viss[u][v])return dp[u][v];if(u==v)return 0;int t1=to[u][v],t2=to[t1][v];if((t1==v)||(t2==v))return 1;dp[u][v]=1;for(auto e:g[v])dp[u][v]+=dfs(t2,e)/(deg[v]+1);dp[u][v]+=dfs(t2,v)/(deg[v]+1);viss[u][v]=true;return dp[u][v];}int main(){memset(dis,0x3f,sizeof dis);memset(to,0x3f,sizeof to);scanf("%d%d%d%d",&n,&m,&s,&t);for(int i=0,u,v;i<m;i++)scanf("%d%d",&u,&v),addedge(u,v),addedge(v,u);for(int i=1;i<=n;i++)bfs(i);for(int i=1;i<=n;i++)for(auto v:g[i])for(int j=1;j<=n;j++)if(dis[i][j]-1==dis[v][j])to[i][j]=min(to[i][j],v);printf("%.3f",dfs(s,t));return 0;}

3. OSU!

题面

链接


随机一个 01 串,第 i 个字符是 1 的概率是 ai .

求极长连续 1 段长度三次方和的期望 .

题解

喜闻乐见 osu!

Ek(x) 表示极长连续 1 段长度 k 次方和(第 x 个是 1)的期望 .

显然 E1(x)=(E1(x)+1)ai,于是可以线性递推 .

平方怎么搞?

E2(x)=(E2(x1)+2E1(x1)+1)ai

为啥?完全平方公式 .

类似

E3(x)=(E3(x1)+3E2(x1)+3E1(x1)+1)ai

目前条件是 x 个是 1,想丢掉它只需要后面加一个 E3(x1)(1ai) 就完了 .

代码空间复杂度 O(n),滚一下可以做到 O(1) .

代码

using namespace std;const int N=100050;int n;double E1[N],E2[N],ans;int main(){scanf("%d",&n);double tmp;for(int i=1;i<=n;i++){scanf("%lf",&tmp);E1[i]=(E1[i-1]+1)*tmp;E2[i]=(E2[i-1]+2*E1[i-1]+1)*tmp;ans+=(3*E2[i-1]+3*E1[i-1]+1)*tmp;}printf("%.1f",ans);return 0;}

4. Red is good

题面

链接


R1B1,随机打乱,依次选取,可以在任意时刻停止 .

问在最优策略下期望取到数的和是多少 .

题解

dp,设答案是 dpR,B,则

dpR,B=max{RR+B(dpR1,B+1)+BR+B(dpR,B11),0}

是不是非常显然

为啥要 max{,0}?因为最优策略 — 停下 .

可以滚一维,但是我不想滚(要过 DarkBZOJ 必须要滚) .

代码

using namespace std;const int N=5141;typedef long long ll;typedef double db;int r,b;db dp[N][N];int main(){scanf("%d%d",&r,&b);for(int i=1;i<=r;i++)dp[i][0]=i;for(int i=1;i<=r;i++)for(int j=1;j<=b;j++)dp[i][j]=max(0.0,1.0*j/(i+j)*(dp[i][j-1]-1)+1.0*i/(i+j)*(dp[i-1][j]+1));ll integer=floor(dp[r][b]),_=floor(dp[r][b]*1e6-integer*1e6);printf("%lld.%06lld",integer,_);return 0;}

5. 守卫者的挑战

题面

链接

题解

dpi,j,k 表示前 i 场挑战赢了 j 场剩余容量为 k 的概率 .

这方案数他妈不是 200×200×200000=8×109 的吗?

容量 k 只有不大于 n 的部分是有用的,所以状态数其实是 8×106 的,可以过~鼓掌熊

有细节

代码

using namespace std;const int N=222;typedef long long ll;typedef double db;int n,l,k;struct thing{db p;int a;thing()=default;bool operator<(const thing&x){return a>x.a;}}a[N];db dp[N][N][N];int main(){scanf("%d%d%d",&n,&l,&k);for(int i=1;i<=n;i++)scanf("%lf",&a[i].p),a[i].p*=0.01;for(int i=1;i<=n;i++)scanf("%d",&a[i].a);sort(a+1,a+1+n);k=min(k,n);dp[0][0][k]=1;for(int i=1;i<=n;i++)for(int j=0;j<i;j++)for(int k=0;k<=n;k++){dp[i][j][k]+=dp[i-1][j][k]*(1-a[i].p);if(a[i].a+k>=0)dp[i][j+1][min(n,a[i].a+k)]+=dp[i-1][j][k]*a[i].p;}db ans=0;for(int j=l;j<=n;j++)for(int k=0;k<=n;k++)ans+=dp[n][j][k];printf("%.6f\n",ans);return 0;}

6. Easy

题面

链接


均匀随机 01 串,求极长连续 1 段期望和

题解

OSU! 弱化版

代码

7. 单选错位

题面

链接


均匀随机序列,第 i 位的取值是 [1,ai]Z .

循环轮换一位,问序列对应位相等个数的期望

题解

期望线性性,拆成相邻两位相同概率 .

古典概型,方案数相除,第 i 位答案就是 min{ai,ai+1}aiai+1i+1 需要轮换 .

代码

using namespace std;const int N=1e7+500;int n,A,B,C,a[N];int main(){scanf("%d%d%d%d%d",&n,&A,&B,&C,a+1);for(int i=2;i<=n;i++)a[i]=((long long)a[i-1]*A+B)%100000001;for(int i=1;i<=n;i++)a[i]=a[i]%C+1;double ans=1.0/max(a[1],a[n]);for(int i=1;i<n;i++)ans+=1.0/max(a[i],a[i+1]);printf("%.3f",ans);return 0;}

8. 列队春游

题面

链接

题解

这题坑啊,连样例都没有 ... ...
唔,我好菜啊 QwQ.

Lemma 1.

E(x)=i=1P(xi)

x 取值的期望等于它大于等于某数的概率之和 .


Proof.
由期望的定义知

E(x)=iiP(x=i)=ii(P(xi)P(xi+1))=iiP(xi)iiP(xi+1)=iiP(xi)i(i1)P(xi)=iP(xi)

证毕 .


Lemma 2

i=1n(ni+1k)=(n+1k+1)


众所周知,(nm)=(n1m)+(n1m1)

然后在把这个式子代到后面的 (n1m1),可以得到

(nm)=i=1n1(im1)

原式 ni+1i1n 时也取1n,从而

LHS=i=1n(ik)=(n+1k+1)=RHS

证毕 .


期望的线性性这步还是很显然的 .

算每个小朋友视野的期望,由期望的定义:

E(d)=i=1niP(d=i)

妈呀这不是 Lemma 1 的形式吗,直接套上

E(d)=i=1nP(di)

然后 P(di) 咋求 .

假设有 k 个不小于小朋友 i 身高的小朋友(这个可以轻易算出来),于是他们就会挡住小朋友 i >_<,只需要排列他们即可 .

古典概型,方案数之比,分母显然是随便放(会挡住小朋友和小朋友自己) Ank+1 .

考虑约束:

  • 会挡住小朋友的人不能在小朋友前面 i1 个位置上 >_<
  • 会挡住小朋友的人不能在小朋友的位置上(草)

这个方案就是 Anik,然后小朋友有 ni+1 种位置可以站,于是再乘一个ni+1 .

综上,有

P(di)=(ni+1)AnikAnk+1

是不是非常简单 .

O(n2) 不能,虽然理论可过,但是分子分母也太大了啊 炸弹熊

推一波式子吧,反正每一步都很显然 .

E(d)=i=1nP(di)=i=1n(ni+1)AnikAnk+1=(nk1)!n!i=1n(ni+1)!(nik)!=(nk1)!n!(k+1)!i=1n(ni+1)!(nik)!(k+1)!=(nk1)!n!(k+1)!i=1n(ni+1k+1)=(nk1)!n!(k+1)!(n+1k+2)=(nk1)!n!(k+1)!(n+1)!(k+2)!(nk1)!=n+1k+2

倒数第三个等号是 Lemma 2.

于是你大力求一遍就完了,O(n) .

代码

using namespace std;typedef double db;const int M=1234;int n,b[M],k;db ans;int main(){scanf("%d",&n);for(int i=1,x;i<=n;i++)scanf("%d",&x),++b[x];for(int i=1;i<=1000;i++){ans+=1.0*b[i]*(n+1)/(n+1-k);k+=b[i];}printf("%.2f",ans);return 0;}

9. 矩形粉刷

题面

链接


n×m 的矩形,每次随机刷掉一个矩形,问 k 次之后期望刷掉了多少个格子

题解

期望的线性性,是个地球人都能想到

然后就变成染色一个点的概率,染一次的概率是 p,染 k 次就是 1(1p)k .

注意染到要讨论一大堆东西计算 /tuu

代码

using namespace std;const int N=1e5+500;typedef long long ll;typedef double db;int k,n,m;db ans=0;inline db sqr(const db&x){return x*x;}int main(){scanf("%d%d%d",&k,&n,&m);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){db p=(sqr((i-1)*m)+sqr((j-1)*n)+sqr((n-i)*m)+sqr((m-j)*n)-sqr((i-1)*(j-1))-sqr((i-1)*(m-j))-sqr((n-i)*(j-1))-sqr((n-i)*(m-j)))/sqr(n*m);ans+=1-pow(p,k);}printf("%.0f",ans);return 0;}

10. 卡牌游戏

题面

链接

题解

每个人获胜的概率只与其在排列中与庄家的相对位置有关

dp(i,j) 为还剩 i 个人时,从庄家数第 j 个人获胜的概率 .

枚举这一次选哪张牌,可得

dp(i,j)=1mkdp(i1,jt+i)

其中 t 是死的人,即 t=(ak1)mod(i+1) .

代码

using namespace std;const int N=1e3+500;typedef long long ll;typedef double db;int k,n,m,a[N];db dp[N][N];int main(){scanf("%d%d",&n,&m);for(int i=1;i<=m;i++)scanf("%d",a+i);dp[1][1]=1;for(int i=2;i<=n;i++)for(int j=1;j<=n;j++)for(int k=1;k<=m;k++){int t=(a[k]-1)%i+1;if(t==j)continue;dp[i][j]+=dp[i-1][(j-t+i)%i]/m;}for(int i=1;i<=n;i++)printf("%.2f%% ",dp[n][i]*100);return 0;}

11. 换教室

题面

链接

题解

代码

12. 奖励关

题面

链接

题解

大水题,状压 dp .

dpk,S 玩了 k 次选了 S

dpi,S=j=1n1nmax{dpi1,S,dpi1,S{j}+aj}

没了 .

代码

using namespace std;const int N=16,EN=(1<<N),K=111;typedef long long ll;typedef double db;int k,n,s[N],p[N];db dp[K][EN];int main(){scanf("%d%d",&k,&n);int _=(1<<n);for(int i=1;i<=n;i++){scanf("%d",p+i);int x;while(~scanf("%d",&x)&&x)s[i]|=(1<<(x-1));}for(int i=k;i>=1;i--)for(int j=0;j<_;j++)for(int k=1;k<=n;k++){db __=dp[i+1][j];if((j&s[k])==s[k])__=max(__,dp[i+1][j|(1<<(k-1))]+p[k]);dp[i][j]+=__/n;}printf("%.6f",dp[1][0]);return 0;}

13. 概率充电器

题面

[链接]

题解

看题猜是换根但是没想出来/qd
由期望的线性性,答案就是每个点通电的概率 .

发现这个的并不太好做,我们求一下每个点不通电的概率 .

分析一下每个点受到哪些贡献:

  1. 自己通电
  2. 子树内通电
  3. 子树外通电

f(u) 为自己通电和子树内通电的概率,则

f(u)=(1pi)vson(u)(f(v)+(1f(v)(1w))

为啥?因为贡献可以拆成:

  • 子树没电
  • 子树有电,但是不通电

g(u) 表示子树外通电概率,我们可以考虑 u 的子树外到底是什么:

  • u 的父亲的子树外
  • u 的父亲的子树除了 u 的子树的部分

第一个贡献显然 .

关于第二个,因为我们是连乘积形式,所以直接除掉儿子的贡献就完了 .

于是就有 u 的子树外有电的概率:

P=g(fa(u))f(fa(u))f(u)+(1f(u))(1w)

然后类似的拆贡献:

  • 子树没电
  • 子树有电,但是不通电

于是就有

g(u)=P+(1P)(1w)

这个从父亲到儿子的 dp 可以自顶向下做 dfs .

注意特判分母为 0 的情况

代码

using namespace std;typedef long long ll;typedef double db;const int N=514114;vector<pair<int,db>>g[N];inline void addedge(int u,int v,db w){g[u].emplace_back(make_pair(v,w));}inline void ade(int u,int v,db w){addedge(u,v,w);addedge(v,u,w);}int n;db p[N],dp1[N],dp2[N];void dfs1(int u,int fa){dp1[u]=1-p[u];for(auto e:g[u]){int v=e.first;db w=e.second;if(v==fa)continue;dfs1(v,u);dp1[u]*=dp1[v]+(1-dp1[v])*(1-w);}}void dfs2(int u,int fa){for(auto e:g[u]){int v=e.first;db w=e.second;if(v==fa)continue;if(dp1[v]+(1-dp1[v])*(1-w)==0)continue;db P=dp2[u]*dp1[u]/(dp1[v]+(1-dp1[v])*(1-w));dp2[v]=P+(1-P)*(1-w);dfs2(v,u);}}int main(){scanf("%d",&n);db w;for(int i=1,u,v;i<n;i++)scanf("%d%d%lf",&u,&v,&w),ade(u,v,w/100);for(int i=1;i<=n;i++)scanf("%lf",p+i),p[i]/=100;dp2[1]=1;dfs1(1,0);dfs2(1,0);db ans=0;for(int i=1;i<=n;i++)ans+=1-dp1[i]*dp2[i];printf("%.6f\n",ans);return 0;}

14. 游走

题面

链接

题解

考虑先统计每个点的期望经过次数 dpi,于是

dpi=ijdpjdegj+[i=1]

因为起点是 1 所以要特判 .

Gauss 消元直接解出来,然后每条边 (u,v) 的期望次数就是

dpudegu+dpvdegv

全部处理出来,然后排序就好了 .

代码

卡精度,我这里让 eps=1010 就过了,不用开 long double .

using namespace std;const int N=555,M=125678;const double eps=1e-10;int n,m,deg[N];double a[N][N],rk[M];vector<int>g[N];inline void addedge(int u,int v){g[u].emplace_back(v);++deg[v];}inline void ade(int u,int v){addedge(u,v);addedge(v,u);}struct Edge{int u,v;Edge(int _,int __):u(_),v(__){};Edge()=default;};vector<Edge>ed;void create(){a[1][n]=1;for(int u=1;u<n;u++){a[u][u]=1;for(auto v:g[u])if(v!=n)a[u][v]=-1.0/deg[v];}}/*此处应有 Gauss 消元*/int main(){scanf("%d%d",&n,&m);for(int i=0,u,v;i<m;i++){scanf("%d%d",&u,&v);ade(u,v);ed.push_back(Edge(u,v));}create();Gauss(n-1);int l=ed.size();for(int i=0;i<l;i++){int u=ed[i].u,v=ed[i].v;if(u!=n)rk[i+1]+=a[u][n]/deg[u];if(v!=n)rk[i+1]+=a[v][n]/deg[v];}sort(rk+1,rk+1+m);double ans=0;for(int i=1;i<=m;i++)ans+=(m-i+1)*rk[i];printf("%.3f\n",ans);return 0;}

15. XOR和路径

题面

链接

题解

由期望定义,可以拆贡献 .

xor 可以按位考虑,于是我们对每位做处理 .

dpu 表示从 un 路径 xor 和为 1 的概率,于是

dpu=1degu(val(u,v)=0dpv+val(u,v)=1(1dpv))

由于可能有后效性,于是考虑 Gauss 消元 .

做完了,注意重边自环的影响 .

时间复杂度 O(wn3),其中 w 是边权的位数 .

代码

using namespace std;const int N=333;const double eps=1e-9;int n,m,deg[N];double a[N][N];vector<pair<int,int>>g[N];inline void addedge(int u,int v,int w){g[u].emplace_back(make_pair(v,w));++deg[v];}inline void ade(int u,int v,int w){addedge(u,v,w);addedge(v,u,w);}void create(int _){a[n][n]=1;for(int u=1;u<n;u++){a[u][u]=deg[u];for(auto e:g[u]){int v=e.first,w=e.second;if(w&(1<<_)){++a[u][v];++a[u][n+1];}else--a[u][v];}}}inline bool z(const double&x){return abs(x)<eps;}/**/int main(){scanf("%d%d",&n,&m);for(int i=0,u,v,w;i<m;i++){scanf("%d%d%d",&u,&v,&w);addedge(u,v,w);if(u!=v)addedge(v,u,w);}double ans=0;for(int i=0;i<32;i++){memset(a,0,sizeof a);create(i);Gauss();ans+=pow(2,i)*a[1][n+1];}printf("%.3f",ans);return 0;}

16. 分手是祝愿

题面

链接

题解

意识流 .

dpi 表示从 i 个需要按的键到 i1 个需要按的键的期望操作次数,于是

dpi=in+nin(dpi+dpi1+1)

代码

using namespace std;const int N=100011,P=100003;typedef long long ll;int n,k,a[N],cc;ll dp[N];void init(){for(int i=n;i>=1;i--){if(!a[i])continue;++cc;for(int j=1;j*j<=i;j++)if(!(i%j)){a[j]^=1;if(j*j!=i)a[i/j]^=1;}}}ll qpow(ll a,ll n){ll ans=1;while(n){if(n&1)ans=ans*a%P;a=a*a%P;n>>=1;}return ans;}ll inv(int x){return qpow(x,P-2)%P;}int main(){scanf("%d%d",&n,&k);for(int i=1;i<=n;i++)scanf("%d",a+i);init();for(int i=n;i>=1;i--)dp[i]=(n+(n-i)*dp[i+1]%P)%P*inv(i)%P;ll ans;if(cc<=k)ans=cc;else{ans=k;for(int i=k+1;i<=cc;i++)ans=(ans+dp[i])%P;}for(int i=1;i<=n;i++)ans=ans*i%P;printf("%lld\n",ans);return 0;}

17, 18

不会,摆烂了 .

posted @   yspm  阅读(76)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
😅​
点击右上角即可分享
微信分享提示