概率期望简单杂题
前言
云剪切板 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\) 走过的路径边权和期望 .
简要题解
简单题,令 \(dp_u\) 为从 \(1\) 到 \(n\) 的版边和期望,则由期望定义知
其中 \(\deg u\) 是 \(u\) 的入度 .
转移顺序是倒着的拓扑序 .
代码
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\) 在点 \(m\),\(B\) 在点 \(c\),每个时刻 \(A,B\) 的走法如下:
- \(A\) 在图上随机游走(\(A\) 可能原地不动)
- \(B\) 的策略是每次选一个最接近 \(A\) 的出点,如果没到 \(A\) 就再走一步 .
每个时刻 \(A\) 先走,\(B\) 后走,问期望走几个时刻 \(A,B\) 相遇 .
简要题解
第二题就放毒瘤,呜呜
首先这个 \(B\) 的迷の走位可以预处理:令 \(to(i,j)\) 表示 \(B\) 在 \(i\),\(A\) 在 \(j\) 时 \(B\) 应该走到哪 .
令 \(dp_{i,j}\) 表示 \(B\) 在 \(i\),\(A\) 在 \(j\) 的方案数,则
- 若 \(i=j\),则 \(dp_{i,j}=0\) .
- 若 \(i\) 走 \(1,2\) 步就到 \(A\) 了,则 \(dp_{i,j}=1\) .
- 若不然,则:
其中 \(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\) 的概率是 \(a_i\) .
求极长连续 \(1\) 段长度三次方和的期望 .
题解
喜闻乐见 osu!
\(\mathbb E^k(x)\) 表示极长连续 \(1\) 段长度 \(k\) 次方和(第 \(x\) 个是 \(1\))的期望 .
显然 \(\mathbb E^1(x)=(\mathbb E^1(x)+1)a_i\),于是可以线性递推 .
平方怎么搞?
为啥?完全平方公式 .
类似
目前条件是 第 \(x\) 个是 \(1\),想丢掉它只需要后面加一个 \(\mathbb E^3(x-1)(1-a_i)\) 就完了 .
代码空间复杂度 \(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
题面
有 \(R\) 个 \(1\),\(B\) 个 \(-1\),随机打乱,依次选取,可以在任意时刻停止 .
问在最优策略下期望取到数的和是多少 .
题解
dp,设答案是 \(dp_{R,B}\),则
是不是非常显然
为啥要 \(\max\{\cdots,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. 守卫者的挑战
题面
题解
令 \(dp_{i, j, k}\) 表示前 \(i\) 场挑战赢了 \(j\) 场剩余容量为 \(k\) 的概率 .
这方案数他妈不是 \(200\times 200\times 200000=8\times 10^9\) 的吗?
容量 \(k\) 只有不大于 \(n\) 的部分是有用的,所以状态数其实是 \(8\times 10^6\) 的,可以过~
有细节
代码
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, a_i]\cap \mathbb Z\) .
循环轮换一位,问序列对应位相等个数的期望
题解
期望线性性,拆成相邻两位相同概率 .
古典概型,方案数相除,第 \(i\) 位答案就是 \(\dfrac{\min\{a_i,a_{i+1}\}}{a_ia_{i+1}}\),\(i+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.
\[\mathbb E(x)=\sum_{i=1}^{\infty}\mathbb P(x\ge i) \]即 \(x\) 取值的期望等于它大于等于某数的概率之和 .
Proof.
由期望的定义知\[\begin{aligned}\mathbb E(x)&=\sum_{i}i\cdot \mathbb P(x=i)\\&=\sum_{i}i(\mathbb P(x\ge i)-\mathbb P(x\ge i+1))\\&=\sum_ii\cdot\mathbb P(x\ge i)-\sum_ii\cdot \mathbb P(x\ge i+1)\\&=\sum_ii\cdot\mathbb P(x\ge i)-\sum_i(i-1)\mathbb P(x\ge i)\\&=\sum_i\mathbb P(x\ge i)\end{aligned} \]证毕 .
Lemma 2
\[\sum_{i=1}^n\dbinom{n-i+1}{k}=\dbinom{n+1}{k+1} \]
众所周知,\(\dbinom nm=\dbinom{n-1}m+\dbinom{n-1}{m-1}\)
然后在把这个式子代到后面的 \(\dbinom{n-1}{m-1}\),可以得到
\[\dbinom nm=\sum_{i=1}^{n-1}\dbinom{i}{m-1} \]原式 \(n-i+1\) 当 \(i\) 取 \(1\dots n\) 时也取\(1\dots n\),从而
\[\begin{aligned}\mathrm{LHS}&=\sum_{i=1}^n\dbinom{i}{k}\\&=\dbinom{n+1}{k+1}&=\mathrm{RHS}\end{aligned} \]证毕 .
期望的线性性这步还是很显然的 .
算每个小朋友视野的期望,由期望的定义:
妈呀这不是 Lemma 1 的形式吗,直接套上
然后 \(\mathbb P(d\ge i)\) 咋求 .
假设有 \(k\) 个不小于小朋友 \(i\) 身高的小朋友(这个可以轻易算出来),于是他们就会挡住小朋友 \(i\) >_<,只需要排列他们即可 .
古典概型,方案数之比,分母显然是随便放(会挡住小朋友和小朋友自己) \(A_{n}^{k+1}\) .
考虑约束:
- 会挡住小朋友的人不能在小朋友前面 \(i-1\) 个位置上 >_<
- 会挡住小朋友的人不能在小朋友的位置上(草)
这个方案就是 \(A_{n-i}^k\),然后小朋友有 \(n-i+1\) 种位置可以站,于是再乘一个\(n-i+1\) .
综上,有
是不是非常简单 .
\(O(n^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\times m\) 的矩形,每次随机刷掉一个矩形,问 \(k\) 次之后期望刷掉了多少个格子
题解
期望的线性性,是个地球人都能想到
然后就变成染色一个点的概率,染一次的概率是 \(p\),染 \(k\) 次就是 \(1-(1-p)^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\) 个人获胜的概率 .
枚举这一次选哪张牌,可得
其中 \(t\) 是死的人,即 \(t=(a_k-1)\bmod (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 .
\(dp_{k, S}\) 玩了 \(k\) 次选了 \(S\)
没了 .
代码
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
由期望的线性性,答案就是每个点通电的概率 .
发现这个的并不太好做,我们求一下每个点不通电的概率 .
分析一下每个点受到哪些贡献:
- 自己通电
- 子树内通电
- 子树外通电
令 \(f(u)\) 为自己通电和子树内通电的概率,则
为啥?因为贡献可以拆成:
- 子树没电
- 子树有电,但是不通电
令 \(g(u)\) 表示子树外通电概率,我们可以考虑 \(u\) 的子树外到底是什么:
- \(u\) 的父亲的子树外
- \(u\) 的父亲的子树除了 \(u\) 的子树的部分
第一个贡献显然 .
关于第二个,因为我们是连乘积形式,所以直接除掉儿子的贡献就完了 .
于是就有 \(u\) 的子树外有电的概率:
然后类似的拆贡献:
- 子树没电
- 子树有电,但是不通电
于是就有
这个从父亲到儿子的 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. 游走
题面
题解
考虑先统计每个点的期望经过次数 \(dp_i\),于是
因为起点是 \(1\) 所以要特判 .
Gauss 消元直接解出来,然后每条边 \((u,v)\) 的期望次数就是
全部处理出来,然后排序就好了 .
代码
卡精度,我这里让 \(eps=10^{-10}\) 就过了,不用开 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 可以按位考虑,于是我们对每位做处理 .
令 \(dp_u\) 表示从 \(u\) 到 \(n\) 路径 xor 和为 \(1\) 的概率,于是
由于可能有后效性,于是考虑 Gauss 消元 .
做完了,注意重边自环的影响 .
时间复杂度 \(O(wn^3)\),其中 \(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. 分手是祝愿
题面
题解
意识流 .
令 \(dp_i\) 表示从 \(i\) 个需要按的键到 \(i-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
不会,摆烂了 .
以下是博客签名,正文无关
本文来自博客园,作者:Jijidawang,转载请注明原文链接:https://www.cnblogs.com/CDOI-24374/p/15834770.html
版权声明:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0)进行许可。看完如果觉得有用请点个赞吧 QwQ