简单概率期望题目总汇

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

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;
}