zoukankan      html  css  js  c++  java
  • 概率与期望

    概率与期望

    1. 算法分析

    数学期望的性质

    1. 设X是随机变量,C是常数,则E(CX)=CE(X)。
    2. 设X,Y是任意两个随机变量,则有E(X+Y)=E(X)+E(Y)。
    3. 设X,Y是相互独立的随机变量,则有E(XY)=E(X)E(Y)。
    4. 设C为常数,则E(C)=C。

    特点分析
    数学期望的题目一般都是DAG,起点唯一,终点很多的情况,这种情况下一般都是dp处理,从终点往起点做dp,答案就是f[1].
    解法可以直接递推:反向建图,然后topsort跑出拓扑序,按照拓扑序做dp;
    也可以记忆化搜索,直接正向做记忆化搜索。

    规律总结
    这里要注意的一点:
    随机变量X = Y1+Y2+Y3+...+Yn(认为Yi里面有ti个元素)
    按照公式E(X) = E(Y1)+E(Y2)+...+E(Yn)
    可是一般计算的时候都是dp[x] = dp[y1]p1+dp[y2]p2+...为什么呢?
    因为递推的过程中,算出dp[y1]时是认为只有t1个元素,而在算dp[x]时,元素个数是t1+t2+t3+...+tn个,所以要把原来算出来的期望dp[y1] * p1表示原先算出来的期望E'需要先转化为当前期望E,二者的转换关系为E' * p1=E
    因此,我们可以得出结论
    从起点开始递推,当前的期望=累加【子状态的期望 * 子状态出现的概率】

    2. 例题

    2.1 期望的线性

    bzoj2969矩形粉刷
    小M粉刷一个大木板。大木板实际上是一个W*H的方阵。小M得到了一个神奇的工具,这个工具只需要指定方阵中两个格子,就可以把这两格子为对角的,平行于木板边界的一个子矩形全部刷好。小M乐坏了,于是开始胡乱地使用这个工具。假设小M每次选的两个格子都是完全随机的(方阵中每个格子被选中的概率是相等的),而且小M使用了K次工具,求木板上被小M粉刷过的格子个数的期望值是多少。

    /* 由于期望具有可加性,因此可以计算出每个格子被染色的概率,加起来即为答案。
    那么一个格子被染色的概率即为1-(每次都不被染色的概率)^k。
    考虑单次染色没有没染的情况:选定的两个点都在左边、上边、右边、下边,但是会发现四个角的部分会计算两次,因此还需要减掉两个点都在左上、左下、右上、右下的情况。然后求幂加起来即可。 */
    #include <cmath>
    #include <cstdio>
    inline double squ(double x)
    {
    	return x * x;
    }
    int main()
    {
    	int k , n , m , i , j;
    	double ans = 0;
    	scanf("%d%d%d" , &k , &n , &m);
    	for(i = 1 ; i <= n ; i ++ )
    		for(j = 1 ; j <= m ; j ++ )
    			ans += 1 - pow((squ((i - 1) * m) + squ((j - 1) * n) + squ((n - i) * m) + squ((m - j) * n)
    				          - squ((i - 1) * (j - 1)) - squ((i - 1) * (m - j)) - squ((n - i) * (j - 1)) - squ((n - i) * (m - j))) / squ(n * m) , k);
    	printf("%.0lf
    " , ans);
    	return 0;
    }
    

    2.2 DAG期望

    acwing217绿豆蛙的归宿
    一个有向无环的连通图,起点为1,终点为N,每条边都有一个长度。数据保证从起点出发能够到达图中所有的点,图中所有的点也都能够到达终点。绿豆蛙从起点出发,走向终点。到达每一个顶点时,如果有K条离开该点的道路,绿豆蛙可以选择任意一条道路离开该点,并且走向每条路的概率为 1/K 。现在绿豆蛙想知道,从起点走到终点所经过的路径总长度的期望是多少?
    N~1e5, M~2N

    #include <bits/stdc++.h>
    
    using namespace std;
    
    int const N = 1e5 + 10;
    int n, m, dout[N], e[N * 2], ne[N * 2], idx, h[N], w[N * 2];
    double f[N];
    
    void add(int a, int b, int c) {
        e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
    }
    
    double dp(int u)
    {
        if (f[u] >= 0) return f[u];  // 搜索过了
        f[u] = 0;
        for (int i = h[u]; ~i; i = ne[i])  // 遍历所有后继
        {
            int j = e[i];
            f[u] += (w[i] + dp(j)) / dout[u];  // 当前状态的期望=所有子状态期望 * 对应概率
        }
        return f[u];
    }
    
    int main() {
        cin >> n >> m;
        memset(h, -1, sizeof h);
        for (int i = 1, a, b, c; i <= m; ++i) {
            scanf("%d %d %d", &a, &b, &c);
            add(a, b, c);
            dout[a]++;
        }
        memset(f, -1, sizeof f);
        printf("%.2lf", dp(1));
        return 0;
    }
    
    #include <bits/stdc++.h>
    
    using namespace std;
    
    int const N = 1e5 + 10;
    int n, m, h[N], e[N * 2], ne[N * 2], idx, w[N * 2], din[N], d[N], a[N], cnt;
    double f[N];
    
    void add(int a, int b, int c) {
        e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
    }
    
    void top_sort() {
        queue<int> q;
        q.push(n);  // 一开始只需要把n放进去即可
        while (q.size())
        {
            auto t = q.front();
            q.pop();
            a[cnt++] = t;
    
            for (int i = h[t]; ~i; i = ne[i])
            {
                int j = e[i];
                d[j]--;
                if (!d[j]) q.push(j);
            }
        }
    }
    
    int main() {
        cin >> n >> m;
        memset(h, -1, sizeof h);
        
        // 反向建图
        for (int i = 1, a, b, c; i <= m; ++i) {
            cin >> a >> b >> c;
            add(b, a, c);
            din[a]++;
        }
        
        memcpy(d, din, sizeof d);  // 复制din,因为拓扑排序会改变din
        top_sort();  // 得到拓扑序
        
        // 按照拓扑序做dp更新
        for (int i = 0; i < cnt; ++i)
        {
            int t = a[i];
            for (int j = h[t]; ~j; j = ne[j])
            {
                int k = e[j];
                f[k] += (w[j] + f[t]) / din[k];
            }
        }
        printf("%.2lf", f[1]);
        return 0;
    }
    

    2.3 无向图期望

    【bzoj3143】[Hnoi2013]游走
    一个无向连通图,顶点从1编号到N,边从1编号到M。
    小Z在该图上进行随机游走,初始时小Z在1号顶点,每一步小Z以相等的概率随机选 择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小Z 到达N号顶点时游走结束,总分为所有获得的分数之和。
    现在,请你对这M条边进行编号,使得小Z获得的总分的期望值最小。

    /* 显然让期望经过次数越大的边编号越小即可,所以只要求出边的期望经过次数就能出解。
    由于边数过大,但是边的期望经过次数可以用它连接的两个点的 期望次数/度数 之和得到,所以只要求出点的期望经过次数就能出解。
    对于每个一般的点y,它的期望经过次数为f[y]=∑exist x→y f[x] / d[x]。
    然后有两个特殊的点:1和n,由于1是起点,所以要算上初始经过的一次,在表达式的右边加1;由于n是终点,到达终点就不能够再经过其它边,所以按照上面的期望经过次数的计算方式,n的期望经过次数应该强制设置为0。(并非部分题解中的1)(所以其实上面的期望经过次数指的是“到达终点前的期望经过次数”,实际上也应该这样理解)
    由于图是没有dp顺序的,所以需要使用高斯消元求出f[i]。
    求出所有点的期望经过次数,然后计算出边的经过次数,排序出解。 */
    #include <bits/stdc++.h>
    
    using namespace std;
    
    int const N = 510;
    const double eps = 1e-11;
    
    int mp[N][N], n, m, d[N];
    double a[N][N];
    struct Edge {
        int u, v;
        double val;
        
        bool operator<(const Edge &w) const{
            return val > w.val;
        }
    }edge[200010];
    
    void gauss(int m, int n)
    {
        int c, r;
        for (c = 0, r = 0; c < n && r < m; c ++, r++)  // 正在找第i元
        {
            // 找出第一个元素最大的那一行
            int t = r; 
            for (int i = r; i < m; i ++ )
                if (fabs(a[i][c]) > fabs(a[t][c]))
                    t = i;
    
            if (fabs(a[t][c]) < eps) {
                r--;
                continue;  // 如果是0,那么不用管
            }
    
            for (int i = c; i < n + 1; i ++ ) swap(a[t][i], a[r][i]);  // 把第一个元素最大的那一行放到第一行
            for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];  // 把第一行的首元消成1
    
            for (int i = r + 1; i < m; i ++ )  // 把后面每行都和刚刚首元消成1的那行做运算
                if (fabs(a[i][c]) > eps)
                    for (int j = n; j >= c; j -- )
                        a[i][j] -= a[r][j] * a[i][c];
        }
        
        // 唯一解,把解放到n+1列
        for (int i = n - 1; i >= 0; i -- )
            for (int j = i + 1; j < n; j ++ )
                a[i][n] -= a[j][n] * a[i][j];
    
        return ;
    }
    
    int main() {
        cin >> n >> m;
        for (int i = 1, a, b; i <= m; ++i) {
            scanf("%d%d", &a, &b);
            edge[i].u = a, edge[i].v = b;
            mp[a][b] = mp[b][a] = 1;
            d[a]++, d[b]++;
        }
        
        for(int i = 1; i < n; i ++)
        {
            a[i - 1][i - 1] = 1;
            for(int j = 1; j <= n; j ++) if(mp[j][i]) a[i - 1][j - 1] = -1.0 / (double)d[j];
        }
        
        a[0][n] = a[n - 1][n - 1] = (double)1;
        
        gauss(n, n);
        
        for(int i = 1 ; i <= m ; i ++ ) edge[i].val = a[edge[i].u - 1][n] / (double)d[edge[i].u] + a[edge[i].v - 1][n] / (double)d[edge[i].v];
        sort(edge + 1, edge + m + 1);
        double res = 0;
        for (int i = 1; i <= m; ++i) res += edge[i].val * i;
        printf("%.3lf", res);
        return 0;
    }
    
  • 相关阅读:
    POCO库——Foundation组件之日期时间DateTime
    POCO库——Foundation组件之加解密Crypt
    POCO库——Foundation组件之缓存Cache
    POCO库——Foundation组件之核心Core
    POCO库——Foundation组件概述
    HP-SOCKET TCP/UDP通信框架库解析
    Notepad++ 使用nppexec插件配置简易开发环境
    Duilib源码分析(五)UI布局—Layout与各子控件
    Breakpad Google的crash捕获、抓取开源库
    Pugixml一种快速解析XML文件的开源解析库
  • 原文地址:https://www.cnblogs.com/spciay/p/13060821.html
Copyright © 2011-2022 走看看