概率期望

•期望计算方法:每种事件的值乘该事件发生的概率

$ 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

\[dp[i][j]表示已经找到i种bug,并存在于j个子系统中,要达到目标状态的天数的期望。\\ 显然,dp[n][s]=0,因为已经达到目标了。而dp[0][0]就是我们要求的答案。\\ dp[i][j]状态可以转化成以下四种:\\ dp[i][j] 发现一个bug属于已经找到的i种bug和j个子系统中——p1=\frac{j}{s}*\frac{i}{n}\\ dp[i+1][j] 发现一个bug属于新的一种bug,但属于已经找到的j个子系统——p2=\frac{j}{s}*\frac{n-i}{n}\\ dp[i][j+1] 发现一个bug属于已经找到的i种bug,但属于新的子系统——p3=\frac{s-j}{s}*\frac{i}{n}\\ dp[i+1][j+1]发现一个bug属于新的一种bug和新的一个子系统——p4=\frac{s-j}{s}*\frac{n-i}{n}\\ 由上述期望计算方法\\ dp[i,j] = p1*dp[i,j] + p2*dp[i+1,j] + p3*dp[i,j+1] + p4*dp[i+1,j+1] + 1;\\ dp[i,j] = ( 1 + p2*dp[i+1,j] + p3*dp[i,j+1] + p4*dp[i+1,j+1] )/( 1-p1 ) = ( 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 \]

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概率

\[设f[ i ] [ j ]表示前 i 秒剩下n个人 j 个人的概率\\ f[i][j]=f[i-1][j-1]*p+f[i-1][j]*(j==n?1:(1-p))\\ 显然n个人都没了就不乘 \]

#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)

\[{(x+1)}^2-x^2=2*x+1\\ 设f[i]表示线性期望,g[i]表示答案\\ 则有f[i]=(f[i-1]+1)*p,g[i]=g[i-1]+(2*f[i-1]+1)*p\\ \]

当然这里不用数组就可

#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不够)

详解blog

#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 ]记录每个点的概率

\[1+1/2+1/4+...+1/dep \]

然后用个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:收集邮票

Solution

#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

见7.26模拟赛

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;
}
posted @ 2020-08-19 21:11  ke_xin  阅读(62)  评论(0编辑  收藏  举报