数学概率期望总结
由于本人很菜概率期望学的不是很好所以特别写一篇总结。。。
收集邮票
这个题代码比较短但是思维含量的确挺高的,加之部分网上题解对于转移方程的描述过于显然,所以可能会有人想不明白这题。
先考虑能不能直接推式子计算,你会发现不仅式子不好推而且好像根本算不出来,于是考虑另一种比较套路的做法,dynamic programming。
直接设当前买到\(i\)种邮票的期望花费为\(f_i\),然后进行转移,会发现又被卡住了,因为买某张邮票的时候钱数与这是第几张邮票有关系,并且买到的有可能是之前已经买到过的邮票,这两者会导致什么情况呢,就是dp有后效性。
那是不是不能dp呢?普通式子好像还是没有什么可以推的于是思考如何消除后效性,假设我们已经知道了买完这次之后还期望买多少次能买完这\(n\)张,那完全可以将费用提前计算然后消除后效性。
于是可以设\(f_i\)为买到\(i\)种邮票后,买齐\(n\)种的期望购买次数,设\(g_i\)表示买完\(i\)种后,买齐\(n\)种的期望购买花费,先考虑\(f_i\)的转移,因为它是用来辅助\(g\)数组进行转移的,想一下买完这张邮票之后会有什么影响,有可能会买到曾经买到的种类,概率为\(\frac{i}n\),然后此时要买齐\(n\)种的期望还是\(f_i\),初学概率dp的时候可能会感觉这种转移有些问题,自己还能转移自己??其实这只是用来推式子,从而将一个无穷尽的过程可视化,因为买邮票完全可以每次都买不齐然后一直买一直买。。。。虽然这是我自己口胡的但是我的确这么理解,然后考虑,买到了一种原来没有买到过的邮票,概率为\(\frac{n-i}n\),然后此时就有了\(i+1\)种邮票,买齐\(n\)种的期望变成了\(f_{i+1}\),也可以从集合的角度去理解,花费1由集合\(i\)扩展为集合\(i+1\),个人感觉这样比较好理解,最后这次花费了一次购买,概率显然是1,所以要加一个1,即
移项合并后发现可以得到\(f_i\)的递推式,
接下来就是\(g_i\)的转移,这个有了\(f\)数组之后就可以通过费用提前计算来消除后效性,转移的思考与上边类似,只说一下不太一样的地方,如果这次买到了曾经买过的\(i\)种,那么以后要再买的\(f_i\)张邮票都要把花费加1,一共加\(f_i\)个费用,这就是费用提前计算,然后进行转移便可。
#include<cstdio>
const int N=1e4+10;
typedef double db;
db f[N],g[N];
int main(){
freopen("D.in","r",stdin);
freopen("D.out","w",stdout);
int n;
scanf("%d",&n);
for(int i=n-1;i>=0;i--)
f[i]=f[i+1]+1.0*n/(n-i);
for(int i=n-1;i>=0;i--)
g[i]=g[i+1]+f[i+1]+f[i]*i/(n-i)+1.0*n/(n-i);
printf("%.2lf\n",g[0]);
return 0;
}
骰子基础版
将一个骰子扔\(n\)次,然后问点数和大于等于\(X\)的概率。
这个感觉归入到计数dp里会好点??emm考虑到扔\(n\)次的情况总数是固定的,所以可以计算出合法的情况总数然后用最原始的计算概率的方法算一下。
计算的时候就定义\(f_{i,j}\)表示当前是第\(i\)次扔,点数和为\(j\)的方案数是多少然后转移就行。
#include<iostream>
#define ll long long
using namespace std;
const int lqs=200;
ll f[lqs][lqs];
ll gcd(ll a,ll b){
return b==0?a:gcd(b,a%b);
}
int main(){
int n,x;
cin>>n>>x;
for(int i=1;i<=6;i++)
f[1][i]=1;
for(int i=2;i<=n;i++)
for(int j=1;j<=6*i;j++)
for(int k=1;k<=6;k++)
if(j>k)
f[i][j]+=f[i-1][j-k];
ll ans=0,cnt=1;
for(int i=1;i<=n;i++)cnt*=6;
for(int i=x;i<=6*n;i++)
ans+=f[n][i];
if(ans==0)cout<<"0";
else if(ans==cnt)cout<<"1";
else {
ll g=gcd(ans,cnt);
cout<<ans/g<<'/'<<cnt/g;
}
return 0;
}
聪聪与可可
一道比较好的概率dp题,不过是记忆化搜索实现的。
其实感觉,最难搞的是猫的走法,因为对于不同的猫的位置和老鼠的位置,猫的走法应该不是一样的,所以预处理出\(nxt\)数组表示猫在\(i\)老鼠在\(j\)时猫的下一步走法,然后就能搞一下了。
不妨设\(f_{i,j}\)表示猫在\(i\)老鼠在\(j\)时抓住老鼠的期望步数,不难得出当\(i==j\)的时候该值为0,否则当\(i\)用一步或两步可以走到\(j\)的时候,该值为1,其余情况可以进行转移,主要就考虑老鼠的下一步往什么方向走就行了,其余的还挺好理解。
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
struct Edge{
int to,nxt;
}e[N<<1];
int h[N],idx;
void Ins(int a,int b){
e[++idx].to=b;e[idx].nxt=h[a];h[a]=idx;
}
int nxt[N][N],dis[N][N];
void bfs(int s){
queue<int> q;q.push(s);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].to;
if(dis[s][v])continue;
dis[s][v]=dis[s][u]+1;
q.push(v);
}
}
dis[s][s]=0;
}
double f[N][N];
int d[N];
double dfs(int s,int t){
if(f[s][t])return f[s][t];
if(s==t)return 0;
int fir=nxt[s][t];
int sec=nxt[fir][t];
if(fir==t||sec==t)return 1;
f[s][t]=1;
for(int i=h[t];i;i=e[i].nxt){
int v=e[i].to;
f[s][t]+=dfs(sec,v)*1.0/(d[t]+1);
}
f[s][t]+=dfs(sec,t)*1.0/(d[t]+1);
return f[s][t];
}
int main(){
int n,m,s,t;
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
int a,b;
scanf("%d%d",&a,&b);
Ins(a,b);Ins(b,a);
d[a]++;d[b]++;
}
memset(nxt,0x3f,sizeof(nxt));
for(int i=1;i<=n;i++)bfs(i);
for(int i=1;i<=n;i++){
for(int j=h[i];j;j=e[j].nxt){
int v=e[j].to;
for(int k=1;k<=n;k++)if(dis[i][k]==dis[v][k]+1)nxt[i][k]=min(nxt[i][k],v);
}
}
printf("%.3lf\n",dfs(s,t));
return 0;
}
OSU
这题也是推一下式子吧,也是考虑用一步差分,长度从\(x\)到\(x+1\)的贡献变化是\((x+1)^3-x^3\)然后推一下式子就能得到这个式子是跟\(x^2\)和\(x\)有关系的,但是注意期望的平方不等于平方的期望,所以要再去推导一下\(x^2\)与\(x\)。
#include<cstdio>
#include<iostream>
typedef double ll;
using namespace std;
int main(){
int n;
scanf("%d",&n);
ll ans=0,E1=0,E2=0;
for(int i=1;i<=n;i++){
ll p;
scanf("%lf",&p);
ans+=(3*E1+3*E2+1)*p;
E2=(E2+2*E1+1)*p;
E1=(E1+1)*p;
}
printf("%.1lf",ans);
}
守卫者的挑战
一道比较显然的概率\(dp\)?,根据题意可以设出状态转移方程\(f_{i,j,k}\)表示当前打到第\(i\)关,背包容量还有\(j\),赢了\(k\)次的概率有多少。背包容量可能是负的所以需要平移一下坐标原点
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=400+10;
typedef double db;
db f[205][N][205],p[N];
int typ[N];
int main(){
int n,L,K;
scanf("%d%d%d",&n,&L,&K);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
p[i]=1.0*x/100;
}
for(int i=1;i<=n;i++){
scanf("%d",&typ[i]);
}
f[0][n][0]=1.0;
for(int i=1;i<=n;i++){
for(int j=0;j<=n*2;j++){
f[i][j][0]+=f[i-1][j][0]*(1-p[i]);
for(int k=1;k<=i;k++){
if(typ[i]+j>=0){
f[i][min(j+typ[i],2*n)][k]+=f[i-1][j][k-1]*p[i];
f[i][j][k]+=f[i-1][j][k]*(1-p[i]);
}
}
}
}
db ans=0;
for(int i=n-K;i<=n*2;i++)
for(int j=L;j<=n;j++)
ans+=f[n][i][j];
printf("%.6lf\n",ans);
return 0;
}
Easy
某一天WJMZBMR在打osu~~~但是他太弱逼了,有些地方完全靠运气:(
我们来简化一下这个游戏的规则
有n次点击要做,成功了就是o,失败了就是x,分数是按comb计算的,连续a个comb就有aa分,comb就是极大的连续o。比如ooxxxxooooxxx,分数就是22+4*4=4+16=20。
Sevenkplus闲的慌就看他打了一盘,有些地方跟运气无关要么是o要么是x,有些地方o或者x各有50%的可能性,用?号来表示。比如oo?xx就是一个可能的输入。
那么WJMZBMR这场osu的期望得分是多少呢?比如oo?xx的话,?是o的话就是oooxx => 9,是x的话就是ooxxx => 4 期望自然就是(4+9)/2 =6.5了
和上边的题目其实差不多,只在是?的时候求一下期望然后其余情况照常处理即可。
#include<cstdio>
#include<iostream>
using namespace std;
const int N=3e5+10;
char s[N];
int main(){
int n;
cin>>n>>s;
double ans=0,len=0;
for(int i=0;i<n;i++){
if(s[i]=='o'){
ans+=2*len+1;
len++;
}else if(s[i]=='x')
len=0;
else {
ans+=(2*len+1)*0.5;
len=(len+1)*0.5;
}
}
printf("%.4lf",ans);
}
单选错位
因为每道题只跟与它交换的那个题有关系,所以考虑一道题移到另一道题的位置上会发生什么,假设选项较小的那个有\(Min\)个,较大的有\(Max\)个,那么选对的概率就是\(\frac{1}{Min}\times \frac{Min}{Max}=\frac{1}{Max}\),然后,然后就没了。
卡牌游戏
这个问题我们发现,这个游戏中,某个人赢的概率与场上还剩谁没什么关系,只跟场上还有多少人和庄家是谁有关,进一步的说,与庄家是谁也没啥关系,只跟庄家与最后赢的这个人的相对位置有关,所以可以定义状态\(f_{i,j}\)为场上还剩下\(i\)个人,和庄家距离\(j\)的人获胜的概率,然后枚举抽到的卡片进行转移,假设抽完卡后下一轮庄家的位置为\(p\),讨论\(p\)与\(j\)的关系,如果\(p==j\)不管,\(p>j\)那两人的距离就是\(p-j\),否则是\(i-(p-j)\)就是环的另一半的长度。
#include<cstdio>
#include<cstring>
#include<algorithm>
const int N=60;
int card[N];
double f[N][N];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)scanf("%d",&card[i]);
f[1][1]=100;
for(int i=2;i<=n;i++){
for(int j=1;j<=i;j++){
for(int k=1;k<=m;k++){
int pos=card[k]%i;
if(pos==0)pos+=i;
if(pos>j)f[i][j]+=f[i-1][i-pos+j]/m;
else if(pos<j)f[i][j]+=f[i-1][j-pos]/m;
}
}
}
for(int i=1;i<=n;i++)printf("%.2lf%% ",f[n][i]);
return 0;
}
换教室
这个题可能跟普通的dp套路差不多吧,首先把题中的限制都扔到状态里边,定义\(f_{i,j,k}\)为考虑前\(i\)个课,申请\(j\),第\(i\)次是否申请,然后转移就行了。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2010;
int dis[N][N],c[N],d[N];
double p[N],f[N][N][2];
int main(){
memset(dis,0x3f,sizeof(dis));
int n,m,v,e;
scanf("%d%d%d%d",&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]);
for(int i=1;i<=v;i++)dis[i][i]=0;
for(int i=1;i<=e;i++){
int a,b,w;
scanf("%d%d%d",&a,&b,&w);
dis[a][b]=dis[b][a]=min(dis[a][b],w);
}
for(int k=1;k<=v;k++)
for(int i=1;i<=v;i++)
for(int j=1;j<=v;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
f[i][j][0]=f[i][j][1]=1e30;
f[1][1][1]=f[1][0][0]=0;
for(int i=2;i<=n;i++)
for(int j=0;j<=m;j++){
f[i][j][0]=min(f[i-1][j][0]+dis[c[i-1]][c[i]],f[i-1][j][1]+p[i-1]*dis[d[i-1]][c[i]]+(1-p[i-1])*dis[c[i-1]][c[i]]);
if(j)f[i][j][1]=min(f[i-1][j-1][0]+p[i]*dis[c[i-1]][d[i]]+(1-p[i])*dis[c[i-1]][c[i]],f[i-1][j-1][1]+p[i-1]*p[i]*dis[d[i-1]][d[i]]+(1-p[i-1])*p[i]*dis[c[i-1]][d[i]]+p[i-1]*(1-p[i])*dis[d[i-1]][c[i]]+(1-p[i-1])*(1-p[i])*dis[c[i-1]][c[i]]);
}
double ans=1e30;
for(int i=0;i<=m;i++)ans=min(ans,min(f[n][i][0],f[n][i][1]));
printf("%.2lf\n",ans);
return 0;
}