概率期望
•期望计算方法:每种事件的值乘该事件发生的概率
$ E(X)=∑x_i P(X=x_i )$
随机的抛掷一枚骰子,所得骰子的点数的数学期望为 (1+2+3+4+5+6)/6 =3.5
例1:POJ 2096找bug
Description
•有s个系统,n种bug,小明每天找出一个bug,可能是任意一个系统的,可能是任意一种bug,即是某一系统的bug概率是1/s,是某一种bug概率是1/n。求他找到s个系统的bug,n种bug,需要的天数的期望。
Solution
poj玄学data %lf过不去,%f才能过。。。诡异
#include <iostream>
#include <cstdio>
using namespace std;
#define siz 1005
int n,s;
double dp[siz][siz];
int main(){
scanf("%d%d",&n,&s);
dp[n][s]=0.0;
for(int i=n;i>=0;i--){
for(int j=s;j>=0;j--){
if(i==n&&j==s)continue;
dp[i][j]=(n*s+(n-i)*j*dp[i+1][j]+i*(s-j)*dp[i][j+1]+(n-i)*(s-j)*dp[i+1][j+1])/(n*s-i*j);
}
}
printf("%.4f\n",dp[0][0]);
return 0;
}
例1:bzoj2134 单选错位
如果a>b,那么有b/a的概率选项在1...b之间,这是又有1/b的概率正确,因此期望为1/a;
如果a<b,那么有a/b的概率选项在1...a之间,这是又有1/a的概率正确,因此期望为1/b。
a=b,显然,总的期望就是1/max(a,b)。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const int p=100000001;
int n,a,b,c,x,y,st;
double ans=0;
int main() {
scanf("%d%d%d%d%d",&n,&a,&b,&c,&x);st=y=x;
for(int i=2;i<=n;i++) {
x=((LL)x*a+b)%p;
ans+=1.0/max(x%c+1,y%c+1);y=x;
}
printf("%.3lf\n",ans+1.0/max(x%c+1,st%c+1));
system("pause");
return 0;
}
例2:排队
Description
有n个人排成一列,, 每秒中队伍最前面的人p的概率走上电梯( 一旦走上就不会下电梯),或者有1-p 的概率不动
问你T秒过后,, 在电梯上的人的数量的期望.
Solution
dp概率
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int t,n;
double p,f[2050][2050],ans=0;//i秒过后,电梯上有j个人的概率
int main() {
scanf("%d%lf%d",&n,&p,&t);
f[0][0]=1;
for(int i=1;i<=t;i++){
f[i][0]=f[i-1][0]*(1-p);
for(int j=1;j<=n;j++)
f[i][j]=f[i-1][j-1]*p+f[i-1][j]*(j==n?1:(1-p));
}
for(int i=1;i<=t;i++)
ans+=f[t][i]*i;
printf("%.6lf\n",ans);
// system("pasue");
return 0;
}
dp期望
for (int i=1;i<=n;i++)
for (int j=1;j<=t;j++) f[i][j]=(f[i-1][j-1]+1)*p+f[i][j-1]*(1-p);
printf("%.6lf",f[n][t]);
例3: Osu(1)
当然这里不用数组就可
#include <iostream>
#include <cstdio>
using namespace std;
int n;
double x=0.0,p,ans;
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%lf",&p);
ans+=(2*x+1)*p;
x=(x+1)*p;
}
printf("%lf\n",ans);
system("pause");
return 0;
}
变式1
平方变立方
#include <iostream>
#include <cstdio>
using namespace std;
int n;
double x=0.0,y=0.0,p,ans;
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%lf",&p);
ans+=(3*y+3*x+1)*p;
y=(y+2*x+1)*p;
x=(x+1)*p;
}
printf("%.1lf\n",ans);
return 0;
}
变式2
len记录线性dp,ans记录期望
#include <iostream>
#include <cstdio>
using namespace std;
int n;
char c;
long double x=0.0,ans=0.0,len;
int main() {
scanf("%d",&n);c=getchar();
for(int i=1;i<=n;i++) {
c=getchar();
if(c=='o') ans+=2*len+1,len++;
else if(c=='x') len=0;
else ans+=len+0.5,len=(len+1)/2;
}
printf("%.4Lf\n",ans);//大写L
// system("pause");
return 0;
}
例4:奖励关
状压期望,反着跑,因为正着跑有些状态不一定能到(k不够)
#include <cstdio>
#include <iostream>
using namespace std;
int k,n,s[1<<16],p[20],ss;
double f[105][1<<16];
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int main() {
k=read();n=read();
for(int i=1;i<=n;i++) {
p[i]=read();
while(1) {
ss=read();
if(ss==0)break;
s[i]|=(1<<(ss-1));
}
}
for(int i=k;i>=1;i--){
for(int state=0;state<(1<<n);state++) {
for(int j=1;j<=n;j++) {
if((s[j]&state)!=s[j]) f[i][state]+=f[i+1][state]; //不满足
else f[i][state]+=max(f[i+1][state],f[i+1][state|1<<(j-1)]+p[j]);
}
f[i][state]/=n;
}
}
printf("%.6lf\n",f[1][0]);
return 0;
}
例5:绿豆蛙的归宿(图上期望)
拓扑序,
法一:正着跑,out记录出边
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
#define N 100005
int n,in[N],out[N],m;
double dp[N],p[N];
vector < pair<int,ll> > g[N];
queue<int>q;
bool vis[N];
int main(){
scanf("%d%d",&n,&m);
ll z;
for(int i=1,x,y;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
g[x].push_back(make_pair(y,z));
in[y]++;out[x]++;
}
for(int i=1;i<=n;i++)
if(!in[i])q.push(i);
vis[1]=1;p[1]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<g[u].size();i++){
int v=g[u][i].first,w=g[u][i].second;
if(!vis[v]){
dp[v]+=(dp[u]+w*p[u])/(double)out[u];
p[v]+=p[u]/(double)out[u];
if(--in[v]==0){
vis[v]=1;q.push(v);
}
}
}
}
printf("%.2lf\n",dp[n]);
return 0;
}
法二:倒着跑,in是反图的出边
trick多起点一终点的推荐反着跑
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
#define N 100005
int n,in[N],c[N],m;
double dp[N];
vector < pair<int,int> > g[N];
queue<int>q;
bool vis[N];
int main(){
scanf("%d%d",&n,&m);
for(int i=1,x,y,z;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
g[y].push_back(pair<int,int>(x,z));
in[x]++;c[x]++;
}
for(int i=1;i<=n;i++){
dp[i]=0;
if(!in[i])q.push(i);
}
vis[1]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<g[u].size();i++){
int v=g[u][i].first,w=g[u][i].second;
dp[v]+=dp[u]+w,vis[v]=1;
in[v]--;
if(in[v]==0){
dp[v]/=c[v];q.push(v);
}
}
}
printf("%.2lf\n",dp[1]);
return 0;
}
例6:聪聪与可可
-
猫先走一步,再走一步;(猫比老鼠先走)
-
老鼠可以不动;
-
猫必须走到离老鼠最近的点,如距离有相同,则选编号最小的点。
-
预处理:
-
由于需要知道猫走一步后的点,所以要预处理。预处理出猫在点i,老鼠在点j,猫的下一个走位 c_nxt$ [i][j] $
-
然而预处理出这个.,就还需要使用SPFA预处理出猫在点i到达所有点最短路径 $ dis[i][j] $
额这里边权是1 SPFA不会重新进队,所以 复杂度大概 $ O(N^2) $????????
-
-
考虑使用概率DP,用$ f[i][j] $ 表示猫在点i,老鼠在点j,猫抓到老鼠的期望步数是多少。
我们进行分类讨论:
- 如果猫和老鼠同点,即 i=j ,则 $ f[i][j]=0 $
- 如果猫走一步或两步可以到达 j ,$ f[i][j]=1 $
- 否则$ f[i][j]=sum(f[sec][k]/(du[j]+1))$ (其中,sec表示猫走两步所到达的位置)
-
这个过程可以使用记忆化搜索完成。
-
最终答案是$ f[s][t] $
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int N=1010;
const int inf=100000000;
double p,ans;
int n,m,cc,kk,du[N],w;
int hd[N<<1],to[N<<1],nxt[N<<1],tot;
inline void add(int x,int y){
to[++tot]=y,nxt[tot]=hd[x];hd[x]=tot;
}
int dis[N][N],c_nxt[N][N];
bool vis[N];
queue<int>q;
void spfa(int s,int *dis){
dis[s]=0;
q.push(s);
while(!q.empty()){
int x=q.front();q.pop();
// if(vis[x])continue;
vis[x]=0;
for(int i=hd[x];i;i=nxt[i]){
int y=to[i];
if(dis[y]>dis[x]+1){
dis[y]=dis[x]+1;
if(!vis[y]){
q.push(y);vis[y]=1;
}
}
}
}
}
bool visit[N][N];
double f[N][N];
double dfs(int u,int v){
if(visit[u][v]) return f[u][v];
if(u==v) return 0;
int fir=c_nxt[u][v];
int sec=c_nxt[fir][v];
if(fir==v||sec==v) return 1;
f[u][v]=1;
f[u][v]+=dfs(sec,v)/(du[v]+1);
for(int i=hd[v];i;i=nxt[i]){
f[u][v]+=dfs(sec,to[i])/(du[v]+1);
}
visit[u][v]=1;
return f[u][v];
}
int main(){
n=read();m=read();cc=read();kk=read();
for(int i=1,x,y;i<=m;i++){
x=read();y=read();
du[x]++;du[y]++;
add(x,y);add(y,x);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=c_nxt[i][j]=inf;
for(int i=1;i<=n;i++)
spfa(i,dis[i]);
for(int x=1;x<=n;x++)
for(int i=hd[x];i;i=nxt[i]){
int y=to[i];
for(int j=1;j<=n;j++)
if(dis[x][j]==dis[y][j]+1)
c_nxt[x][j]=min(c_nxt[x][j],y);
}
printf("%.3lf",dfs(cc,kk));
return 0;
}
例7:康娜的线段树
p[ i ]记录每个点的概率
然后用个sum[ ]维护 p[ ]的前缀和
区间加就 (sum[r] - sum[l-1] )*add
#include <iostream>
#include <cstdio>
#include <cctype>
using namespace std;
const int MAXN=1e6+5;
int n,m,qwq,depth[MAXN],x[MAXN];
long double p[MAXN],sum[MAXN],ans;
inline int read() {
int x=0;int f=1;char ch=getchar();
while(!isdigit(ch)){ch=='-'&&(f=-1);ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void dfs(int l,int r,int dep) {
if(l==r) {
depth[l]=dep;
return;
}
int mid=(l+r)>>1;
dfs(l,mid,dep+1);
dfs(mid+1,r,dep+1);
}
int main() {
n=read(),m=read(),qwq=read();
for(int i=1;i<=n;i++)
x[i]=read();
dfs(1,n,1);
for(int i=1;i<=n;i++) {
p[i]=2-(long double)(1.0/(1<<(depth[i]-1)));
printf("%Lf ",p[i]);
ans+=x[i]*p[i];
sum[i]=sum[i-1]+p[i];
}
for(int i=1;i<=m;i++) {
int l=read(),r=read(),add=read();
ans+=(sum[r]-sum[l-1])*add;
printf("%lld\n",(long long)(ans*qwq));
}
return 0;
}
例8:收集邮票
#include <cstdio>
#include <iostream>
using namespace std;
int n;
double ans[10005],num[10005];
int main() {
scanf("%d",&n);
num[n]=0;ans[n]=0;
for(int i=n-1;i>=0;i--) {
num[i]=num[i+1]+(1.0*n)/(1.0*(n-i));
ans[i]=ans[i+1]+num[i+1]+(1.0*n/(1.0*(n-i)))+num[i]*(1.0*i/(1.0*(n-i)));
}
printf("%.2lf",ans[0]);
return 0;
}
例9:礼物(gift)
Description
Solution
最大喜悦值一定就是所有物品喜悦值之和。
现在问题 转化成:n种物品,每次按一定概率拿一件物品,问拿齐所有种 类物品的期望次数是多少。
观察到N 比较小,可以考虑状压DP。设二进制状态S 表示 当前有哪些物品已经拿了,
转移:
$ f_s=\sum{f_{s’}}$ * \(p[i]+ (1-\sum{p[i]})\) * $f_s+1 $
进一步化简 $ \sum{p[i]}f_s=\sum{f_{s’}}p[i]+1 $
最终状态 $ f[(1<<n)-1] $
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=21;
double p[N],f[1<<N];
int n,w[N],cnt[1<<N];
long long ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lf%d",&p[i],&w[i]);
ans+=w[i];
}
printf("%lld\n",ans);
f[0]=0;
for(int s=1;s<(1<<n);s++)
cnt[s]=cnt[s&(s-1)]+1;
for(int s=1;s<(1<<n);s++){
double sump=0;
for(int i=1;i<=n;i++){
if((s>>(i-1)&1))
f[s]+=p[i]*f[s^(1<<(i-1))],sump+=p[i];
}
f[s]=(f[s]+1)/sump;
}
printf("%.3lf",f[(1<<n)-1]);
return 0;
}