概率期望学习笔记
简单概率期望题目总汇
由于自己初学概率期望,学的都是简单题,就不分开写博客了.
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\n",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\n",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\n",y,f[y]);
in[y]--;
if(!in[y]) q.push(y);
}
}
printf("%.2lf\n",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\n",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\n",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\n",y[0] / (1 - x[0]));
}
return 0;
}