简单概率期望题目总汇
由于自己初学概率期望,学的都是简单题,就不分开写博客了.
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;
}