概率期望学习笔记

简单概率期望题目总汇

由于自己初学概率期望,学的都是简单题,就不分开写博客了.

51Nod 1639绑鞋带

非常入门的概率期望题目。但因为题目意思比较恶心.

一共有(2 * n)个鞋头,第(i)次操作前还有(2 * n - (i - 1) * 2)个鞋头,由于我们选出一个后,它不能和自己绑,也不能和和它在同一条链上的绑。

所以

(f_{i + 1}= f_i * frac{2 * n - (i - 2) * 2 - 2}{2 * n - (i - 1) * 2 - 1})

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 1e3 + 3;
double f[N];
int n;
double s;
int main(){
	scanf("%d",&n);
	f[1] = 1;
	for(int i = 1;i <= n;++i){
		double last = (double)2 * n - (i - 1) * 2 - 1;
		f[i + 1] = f[i] * ((last - 1.0) / last);
	}
	printf("%.6lf
",f[n]);
	return 0;
}

BZOJ1419

较简单概率期望DP,然而我还是不会做.

概率期望DP在很多时候都应当倒推.

这道题目网上题解的状态定义比较模糊,蒟蒻看不懂,在这里说一下自己的理解

(f_{i,j})表示当前桌子上还有(i)个红牌未翻开(j)个黑牌未被翻开的,你的期望最优得分(再多得多少分)

(f_{i,j} = (f_{i - 1,j} + 1)* frac{i}{i + j} + (f_{i ,j - 1} - 1) * frac{j}{i + j})

 #include<cstdio>
#include<iostream>
#include<cstring>
#include<cctype>
#include<algorithm>
using namespace std;
const int N = 5e3 + 3;
double f[2][N];
int cnt = 0;
int b,r;
int n;
double ans;
int main(){
	scanf("%d%d",&r,&b);
	for(int i = 1;i <= r;++i){
		cnt ^= 1;f[cnt][0] = i;
		for(int j = 1;j <= b;++j){
			f[cnt][j] = max(0.0,1.0 * (f[cnt ^ 1][j] + 1) * (1.0 * i / (i + j))
			 + 1.0 * ((f[cnt][j - 1] - 1) * (1.0 * j / (i + j))) );	
		}
	}
	printf("%.6lf
",f[cnt][b] - 5e-7);
	return 0;	
}

BZOJ3036

同样是概率期望的入门题目.

错误:入度统计反了

我们依旧考虑建反图逆推(f_i)表示(i - n)的期望路径长度

倒着DP即可

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cctype>
#include<queue>
#include<algorithm>
using namespace std;
const int N = 3e5 + 3;
int tot,n,m;
double f[N];
int head[N],in[N],d[N];
queue <int> q;
struct edge{
	int to;
	int nxt;
	int data;	
}e[N];
inline void add(int x,int y,int z){
	e[++tot].data = z; 
	e[tot].nxt = head[x];
	e[tot].to = y;
	head[x] = tot;	
}
inline void spfa(){
	while(!q.empty()){
		int k = q.front();
		q.pop();
		for(int i = head[k];i;i = e[i].nxt){
			int y = e[i].to;
			f[y] += (f[k] + 1.0 * e[i].data) / (1.0 * d[y]);
		//	printf("%d %lf
",y,f[y]);
			in[y]--;
			if(!in[y]) q.push(y);
		}
	}
	printf("%.2lf
",f[1]);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= m;++i){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		add(y,x,z);
		in[x]++;	
		d[x]++;
	}
	q.push(n);
	spfa();
	return 0;	
}

HDU4576

首先放一下自己的错误:滚动数组将(f_{cnt,j}=.....)写成了(f_{cnt,i} += .....)进行转移

依旧是比较基础的概率期望的DP

(f_{i,j})表示第(i)次操作完成之后在(j)位置的概率

(f_{i,j} = f_{i - 1,j - w} * 0.5 + f_{i - 1,j + w} * 0.5)

注意当(j - w)(j + w)越界的时候特判一下。细节见代码

#include<iostream>
#include<cctype>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 205;
double f[2][N];
inline int read(){
	int v = 0,c = 1;char ch = getchar();
	while(!isdigit(ch)){
		if(ch == '-') c = -1;
		ch = getchar();
	}
	while(isdigit(ch)){
		v = v * 10 + ch - 48;
		ch = getchar();	
	}
	return v * c;
}
int main(){
	while(1){
		int n = read(),m = read(),l = read(),r = read();
		if(n + m + l + r == 0) break;
		for(int i = 0;i < 2;++i)
			for(int j = 1;j <= n;++j) f[i][j] = 0;
		int cnt = 0;f[0][1] = 1;
		double ans = 0;
		for(int i = 1;i <= m;++i){
			int w = read() % n;
			cnt ^= 1;
			for(int j = 1;j <= n;++j){
				f[cnt][j] = f[cnt ^ 1][j - w <= 0 ? n - (w - j) : j - w] * 0.5 + 
				 f[cnt ^ 1][j + w > n ? j + w - n : j + w] * 0.5;	
			}
		}
		for(int i = l;i <= r;++i) ans += f[cnt][i];
		printf("%.4lf
",ans);
	}
	return 0;	
}

POJ2029

这道题目没有犯SB错误

我们想一下

(f_{i,j})表示已经发现(i)种漏洞已经在(j)个系统发现了漏洞达到(f_{n,s})的期望天数

借用这位大佬的图

那么结合上面的图

(f_{i,j} = (f_{i + 1,j + 1} + 1) *frac{(n - i) * (s - j)}{n * s} +(f_{i,j + 1} + 1)* frac{i*(s - j)}{n * s} + (f_{i + 1,j} + 1)*frac{(n - i)*j}{n*s}+ (f_{i,j} + 1) * frac{i * j}{n*s})

之后我们设

(P_1 = frac{(n - i) * (s - j)}{n * s})

(P_2 = frac{(n - i)*j}{n*s})

(P_3 =frac{i*(s - j)}{n * s})

(P_4 = frac{i * j}{n*s})

我们将上式化开

(f_{i,j}*(1 - P_4) = f_{i + 1,j + 1}*P_1 + f_{i + 1,j} * P_2+f_{i,j + 1}*P_3 + (P_1+P_2+P_3+P_4))

((P_1+P_2+P_3+P_4) = 1)

((1 - P_4))除到右边就好了

#include<cstdio>
#include<iostream>
#include<cstring>
#define p1 (1.0 * (n - i) * (s - j) / (n * s))
#define p2 (1.0 * (n - i) * j / (n * s))
#define p3 (1.0 * i * (s - j) / (n * s))
#define p4 (1.0 * i * j / (n * s))
using namespace std; 
const int N = 1e3 + 3;
double f[N][N];
int main(){
	int n,s;
	scanf("%d%d",&n,&s);
	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 + 1] * p1 + f[i + 1][j] * p2 + f[i][j + 1] * p3 + 1) / (1 - p4)
			);
		}
	printf("%.4lf
",f[0][0]);
	return 0;
}	

ZOJ3329

一道非常好的概率期望DP

我们设掷出和(s)的几率为(P_s)(特别的,将(a,b,c)那一组设为(P_0)),(f_i)为获得i分数的期望次数

很明显的转移方程(倒推)

(f_i = f_0*P_o + sum f_{i + k} * P_k + 1)

之后我们发现了一个非常恶心的事情。就是每次转移都和(f_0)有关,而(f_0)是我们求得最终答案

我们试着化简一下,因为每个(f)的式子都与(f_0)有关我们设

(f_{i} = f_0*ai+bi)

那么有

(f_{i + k} = f_0*a_{i + k} + b_{i + k})

我们将上式带回原式子

(f_i = f_0*P_0+sum((f_0*a_{i + k} + b_{i + k})*P_k) + 1)

试着整理一下

(f_i = f_0*(P_0 + sum(a_{i + k} * P_k)) + sum b_{i + k} * P_k + 1)

又因为

(f_i = f_0 * a_i + b_i)

我们就可以得到

$a_i=P_0 + sum(a_{i + k} * P_k) $

(b_i = sum b_{i + k}*P_k + 1)

又因为

(f_0 = f_0*a_0+b_0)

所以

(f_0 = frac{b_0}{(1 - a_0)})

我们得到了(a)(b)的逆推式子,又知道(f_0)(a_0)(b_0)的关系,就好做

#include<cstdio>
#include<iostream>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 1005;
double f[N];
double x[N],y[N],P[N];
int k1,k2,k3,a,b,c,n;
int main(){
	int T;scanf("%d",&T);
	while(T--){
		memset(x,0,sizeof(x));
		memset(P,0,sizeof(P));
		memset(y,0,sizeof(y));
		scanf("%d%d%d%d%d%d%d",&n,&k1,&k2,&k3,&a,&b,&c);
		int sum = k1 + k2 + k3;double Pi = 1.0 / k1 / k2 / k3;
		for(int i = 1;i <= k1;++i)
			for(int j = 1;j <= k2;++j)
				for(int k = 1;k <= k3;++k) if(a != i || b != j || c != k) P[i + j + k] += Pi;
		for(int i = n;i >= 0;--i){
			x[i] = Pi,y[i] = 1;
			for(int s = 3;s <= sum;++s)
				x[i] += x[i + s] * P[s],y[i] += y[i + s] * P[s];
		}
		printf("%.15lf
",y[0] / (1 - x[0]));
	}
	return 0;
}	
原文地址:https://www.cnblogs.com/wyxdrqc/p/10630402.html