概率期望dp
1.codevs1060 搞笑世界杯
搞笑世界杯的球票出售方式也很特别,它们只准备了两种球票.A 类
票 免费球票 B 类票 双倍价钱球票.购买时由工作人员通过掷硬币决定,投到正面
的买A类票, 反面的买B类票.并且主办方不可能倒贴钱,所以他们总是准备了同样多的A类票和B类票
你和你的朋友想计算一下排在队尾的两个人同时拿到一种票的概率是多少
(包括同时拿A 类票或B类票) 假设工作人员准备了2n 张球票,其中n 张A类票,n 张B类票,并且排在队伍中的人每人必须且只能买一张球票(不管掷到的是该买A 还是该买B).
思路:先说一个比较坑爹的事情,读入的是2n,不是n。f[i][j]表示第i人买票时买了j张A票的概率。对于j有三种情况:
1)j=0:f[i][j]=f[i-1][j]*0.5; 2)0<j<n:f[i][j]=(f[i-1][j]+f[i-1][j-1])*0.5; 3)j=n:f[i][j]=f[i-1][j]+f[i-1][j-1]*0.5。
初始化f[0][0]=1;输出f[n*2-2][n]*2(因为最后两张可以全是A或B);
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
double f[3000][3000];
int main()
{
int n,i,j;
scanf("%d",&n);n/=2;
f[0][0]=1;
for (i=1;i<=2*n;++i)
for (j=0;j<=n;++j)
{
if (j>i) continue;
if (j==0) f[i][j]=f[i-1][j]*0.5;
else
{
if (j<n) f[i][j]=(f[i-1][j]+f[i-1][j-1])*0.5;
else f[i][j]=f[i-1][j]+f[i-1][j-1]*0.5;
}
}
printf("%.4f\n",f[n*2-2][n]*2);
}
2.poj2096 Collecting Bugs
题意:
一个软件有s个子系统,会产生n种bug。
某人一天发现一个bug,这个bug属于某种bug,发生在某个子系统中。
求找到所有的n种bug,且每个子系统都找到bug,这样所要的天数的期望。
需要注意的是:bug的数量是无穷大的,所以发现一个bug,
出现在某个子系统的概率是1/s,属于某种类型的概率是1/n。
思路:
f[i][j]表示已经找到i种bug,j个系统的bug,达到目标状态的天数的期望
f[n][s]=0;要求的答案是f[0][0];
f[i][j]可以转化成以下四种状态:
f[i][j],发现一个bug属于已经有的i个分类和j个系统。概率为(i/n)*(j/s);
f[i][j+1],发现一个bug属于已有的分类,不属于已有的系统.概率为 (i/n)*(1-j/s);
f[i+1][j],发现一个bug属于已有的系统,不属于已有的分类,概率为 (1-i/n)*(j/s);
f[i+1][j+1],发现一个bug不属于已有的系统,不属于已有的分类,概率为 (1-i/n)*(1-j/s);
写开之后移项并整理可得方程。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1011
using namespace std;
int n,m,s,cnt;
double f[N][N],ans;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int main()
{
n=read();s=read();
f[n][s]=0;
for(int i=n;i>=0;i--)
for(int j=s;j>=0;j--)
if(i!=n || j!=s)
f[i][j]=(f[i+1][j]*(n-i)*j+f[i][j+1]*(s-j)*i+f[i+1][j+1]*(n-i)*(s-j)+n*s)/(n*s-i*j);
printf("%.4f\n",f[0][0]);
}
3.bzoj3450 Easy
题意:
有n次点击要做,成功了就是o,失败了就是x,分数是按comb计算的,连续a个comb就有a*a分,comb就是极大的连续o。
比如ooxxxxooooxxx,分数就是2*2+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了
思路:
f[i]表示到第i位的得分期望
L[i]表示已i结尾的长度期望
可知如果是o:L[i]=L[i-1]+1 由(L+1)^2=L^2+2*L+1可得 f[i]=f[i-1]+2*L[i-1]+1
如果是x:f[i]=f[i-1],L[i]=0;
如果是?:L[i]=(L[i-1]+1)/2 f[i]=(2*f[i-1]+2*L[i-1]+1)/2;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 300007
using namespace std;
int n,m,k,cnt;
double ans,f[N],L[N];
char s[N],ch[N];
int main()
{
scanf("%d",&n);scanf("%s",s+1);
f[0]=0;n=strlen(s+1);
for(int i=1;i<=n;i++)
{
if(s[i]=='o') L[i]=L[i-1]+1,f[i]=f[i-1]+2*L[i-1]+1;
else if(s[i]=='x') f[i]=f[i-1];
else if(s[i]=='?') L[i]=(L[i-1]+1.0)/2.0,f[i]=f[i-1]+L[i-1]+0.5;
}
printf("%.4lf\n",f[n]);
return 0;
}
4.bzoj1076 [SCOI2008]奖励关
题意:
系统将依次随机抛出k次宝物,
每次你都可以选择吃或者不吃(必须在抛出下一个宝物之前做出选择,且现在决定不吃的宝物以后也不能再吃)。
宝物一共有n种,系统每次抛出这n种宝物的概率都相同且相互独立。也就是说,即使前k-1次系统都抛出宝物1(
这种情况是有可能出现的,尽管概率非常小),第k次抛出各个宝物的概率依然均为1/n。 获取第i种宝物将得到Pi
分,但并不是每种宝物都是可以随意获取的。第i种宝物有一个前提宝物集合Si。只有当Si中所有宝物都至少吃过
一次,才能吃第i种宝物(如果系统抛出了一个目前不能吃的宝物,相当于白白的损失了一次机会)。假设你
采取最优策略,平均情况你一共能在奖励关得到多少分值?
思路:
看到n很小可以状压
首先第一眼可以想到f[i][sta]表示到第i轮,物品选择状态为sta的最大期望值。
但是这样有个问题,可能到第i轮无法达到sta这个状态,但是也被当做了合法往后进行转移。
所以考虑倒退,这样状态就更改为f[i][sta]表示1~i-1轮能够到达sta这个状态,i到k轮的最大期望值。
这样就可以倒退,枚举下一个物品选不选进行转移了。
注意一点
这里求的是期望值,上面求的东西覆盖了第i轮取了所有n种宝物的情况
所以在每一个状态计算完之后,把f[i][sta]除以n即为期望平均值。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 107
using namespace std;
int n,k,cnt,need[N];
double val[16],f[N][1<<16];
inline int read()
{
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int main()
{
int x;
k=read();n=read();
for(int i=1;i<=n;i++)
{
scanf("%lf",&val[i]);x=read();
while(x) need[i]|=(1<<x-1),x=read();
}
for(int i=k;i;i--)
for(int sta=0;sta<=(1<<n)-1;sta++)
{
for (int j=1; j<=n; j++)
if ((sta&need[j])==need[j])
f[i][sta]+=max(f[i+1][sta],f[i+1][sta|(1<<(j-1))]+val[j]);
else
f[i][sta]+=f[i+1][sta];
f[i][sta]/=(double)n;
}
printf("%.6lf\n",f[1][0]);
return 0;
}
5.bzoj4720 换教室(NOIP2016)
大意:一个无向带权图,给定两条长度相等的路径,开始时可以决定以后每一步时是否申请切换路径(不一定成功)
最多申请次数给定,求期望的花费最小值。
思路:f[i][j][0], f[i][j][1]表示当前在第i个时间段,申请了j个教室,0表示申请不通过,1表示申请通过,最小耗费体力的期望值。
当前时间段不申请:
(1)上个时间段未申请,只有一条路径
(2)上个时间段申请了,根据上个时间段申请成败概率决定此时路径(2条)
当前时段申请:
(1)上个时间段未申请,根据这个时间段申请成败概率决定此时路径(2条)
(2)上个时间段申请了,分别根据两个时间段的成败概率决定此时路径(4条)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<cstring> #define inf 0x3f3f3f3f #define N 2001 #define M 301 using namespace std; int n,m,x,y,v,e,z,lim,cnt; double f[N][N][2],g[N][N][2],c[N],ans; int dis[M][M],a[N],b[N]; inline int read() { int x=0,f=1;char c=getchar(); while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();} return x*f; } void init() { for(int i=0;i<=v;i++)for(int j=0;j<=v;j++) if (i==j)dis[i][j]=0; else dis[i][j]=1e9; for(int i=0;i<N;i++) for(int j=0;j<N;j++) f[i][j][0]=f[i][j][1]=1e9; f[1][0][0]=0;f[1][1][1]=0; } void floyd() { for (int k=1; k<=v; k++) for (int i=1; i<=v; i++) for (int j=1; j<=v; j++) if (dis[i][k] + dis[k][j] < dis[i][j]) dis[i][j] = dis[i][k] + dis[k][j]; } int main() { n=read();m=read();v=read();e=read(); for(int i=1;i<=n;i++) a[i]=read(); for(int i=1;i<=n;i++) b[i]=read(); for(int i=1;i<=n;i++) scanf("%lf",&c[i]); init(); for(int i=1;i<=e;i++) { x=read();y=read();z=read(); if (x==y) continue; if (z<dis[x][y])dis[x][y]=z; dis[y][x]=dis[x][y]; } floyd(); for(int i=2;i<=n;i++) { lim=min(i,m);f[i][0][0]=f[i-1][0][0]+dis[a[i-1]][a[i]]; for(int j=1;j<=lim;j++) { f[i][j][0]=min(f[i-1][j][0]+dis[a[i-1]][a[i]],f[i-1][j][1]+dis[a[i-1]][a[i]]*(1.0-c[i-1])+dis[b[i-1]][a[i]]*c[i-1]); //这个点不换 上个点换或不换,换的话成功或失败。 if(j>=1) { f[i][j][1]= //这个点换的方程 min(f[i-1][j-1][0]+dis[a[i-1]][b[i]]*c[i]+ //上一个点不换,这个点成功 dis[a[i-1]][a[i]]*(1.0-c[i]), //上一个点不换,这个点失败 f[i-1][j-1][1]+dis[b[i-1]][b[i]]*c[i-1]*c[i]+//上一个点成功,这个点成功 dis[b[i-1]][a[i]]*c[i-1]*(1.0-c[i])+ //上一个点成功,这个点失败 dis[a[i-1]][b[i]]*(1.0-c[i-1])*c[i]+ //上一个点失败,这个点成功 dis[a[i-1]][a[i]]*(1.0-c[i-1])*(1.0-c[i])); //上一个点失败,这个点失败 } } } ans=f[n][0][0]; for(int i=1;i<=m;i++) ans=min(ans,min(f[n][i][0],f[n][i][1])); printf("%.2lf\n",ans); return 0; } Code