• 学记笔记 $ imes$ 巩固 · 期望泛做$Junior$


    最近泛做了期望的相关题目,大概(Luogu)上提供的比较简单的题都做了吧(233)

    好吧其实是好几天之前做的了,不过因为太颓废一直没有整理……

    (Task1) 期望的定义

    在概率论和统计学中,数学期望((mean))(或均值,亦简称期望)是试验中每次可能结果的概率乘以其结果的总和,是最基本的数学特征之一。它反映随机变量平均取值的大小
    需要注意的是,期望值并不一定等同于常识中的“期望”——“期望值”也许与每一个结果都不相等。期望值是该变量输出值的平均数。期望值并不一定包含于变量的输出值集合里。
    大数定律规定,随着重复次数接近无穷大,数值的算术平均值几乎肯定地收敛于期望值。
    ——(baidu)百科

    (hhh)其实意思就是概率的加权(平均值),此处需要注意,这个平均值是逻辑上的而并非运算上的

    (Task2) 期望的水题们

    ( m{color{red}{Description cdot 1}})

    (color{skyblue}{LuoguP3802})

    现在帕琪有7种属性的能量晶体,分别为(a_1,a_2,a_3,a_4,a_5,a_6,a_7)(均为自然数),每次释放魔法时,会随机消耗一个现有的能量晶体,然后释放一个对应属性的魔法。

    现在帕琪想知道,她释放出帕琪七重奏的期望次数是多少,可是她并不会算,于是找到了学OI的你

    其中帕琪七重奏的触发条件是:连续释放的7个魔法中,如果魔法的属性各不相同,就能触发一次帕琪七重奏。

    ( m{color{red}{Solution cdot 1}})

    这个题中,由于权值是“次数”,所以就直接忽略掉,当作概率做。那么接下来考虑,对于一个任意的排列来讲,他满足条件的概率大概可以如下表示:

    (N = sum a_i),那么我们的答案大概可以写成$$P(N) cdot frac{a_1}{N} frac{a_2}{N-1} frac{a_3}{N-2} frac{a_4}{N-3} frac{a_5}{N-4} frac{a_6}{N-5} frac{a_7}{N-6}$$其中(P(N))表示排列个数。那么我们思考,题目要求的是连续,而在(N)个元素中,总共有(N-6)个连续区间长度为(7)的区间,每个区间还有(7!)种排列,所以(P(N) = 7!(N - 6))那么答案就很简单了。


    简单总结一下,这个地方有个坑,就是你不知道为啥要乘上一个(N-6),因为题目中要求的是连续所以这样——这很浅显。我们这个地方需要拐过的一个弯是,概率已知的情况下,我们需要考虑全部情况而不是合法情况,这是萌新的一个坑(比如我当时就思索了好久(stO))。

    还有一个地方需要仔细理解,就是我们的

    #include <cstdio>
    #include <iostream>
    
    using namespace std ;
    double N, A1, A2, A3, A4, A5, A6, A7 ;
    
    int main(){
    	cin >> A1 >> A2 >> A3 >> A4 >> A5 >> A6 >> A7 ;
    	N = A1 +  A2 + A3 + A4 + A5 + A6 + A7 ;
    	printf("%.3lf",  5040.0 * A1 / N * A2 / (N - 1) * A3 / (N - 2) 
    	* A4 / (N - 3) * A5 / (N - 4) * A6 / (N - 5) * A7 ) ;
    }
    

    $ m{color{red}{Description cdot 2}} $

    (color{skyblue}{LuoguP4316})

    给出一个有向无环图,起点为(1)终点为(N),每条边都有一个长度,并且从起点出发能够到达所有的点,所有的点也都能够到达终点。绿豆蛙从起点出发,走向终点。 到达每一个顶点时,如果有K条离开该点的道路,绿豆蛙可以选择任意一条道路离开该点,并且走向每条路的概率为 (frac{1}{K}) 。 现在绿豆蛙想知道,从起点走到终点的所经过的路径总长度期望是多少?

    $ m{color{red}{Solution cdot 2}} $

    ……这就是个水题……由于是(DAG),所以我们完全可以计算出到达每个点的概率,乘上边权即可。

    ……实在没啥好说的了(qwq)……不理解为什么某谷定的难度是提高组难度(233)

    #include <cstdio>
    #include <iostream>
    #define MAXN 100010
    
    using namespace std ;
    struct edge{
        int to, next ; double v ;
    }e[MAXN << 1] ; int head[MAXN], cnt ;
    int N, M, A, B, C, i ; double Deg[MAXN], Ans ;
    
    inline void add(int u, int v, double w){
        e[++cnt].to = v, e[cnt].v = w ;
        Deg[u] ++, e[cnt].next = head[u], head[u] = cnt ;
    }
    inline void dfs(int now, double dist){
        dist /= Deg[now] ;
        for(int k = head[now] ; k ; k = e[k].next){
            Ans += e[k].v * dist ;
            dfs(e[k].to, dist) ;
        }
    }
    inline int qr(){
        int k = 0 ; char c = getchar() ;
        while (c < '0' || c > '9') c = getchar() ;
        while (c <= '9' && c >= '0') k = (k << 1) + (k << 3) + c - 48, c = getchar() ;
        return k ;
    }
    int main(){
        cin >> N >> M ;
        for(i = 1; i <= M; ++ i )
            A = qr(), B = qr(), C = qr(), add(A, B, C) ;
        dfs(1, 1) ;
        printf("%.2lf", Ans) ;
    }
    
    
    

    ( m{color{red}{Description cdot 3}})

    (color{skyblue}{LuoguP1297})

    好的,这个题不是那么的水,是一个不错的题。

    $ m{color{red}{Solution cdot 3}} $

    我们思考两个思路:

    ·$ 1$

    我们考虑如果所有的(a_i)都相等,那么做对每个题的概率都还是$frac{1}{a_i} $

    好像很显然?但是我们算的时候不是这么算的,我们考虑此时做对一道题的概率应该是(P =)选对第一项(+)选对第二项(+ cdots +) 选对第(a_i)项。那么我们思考,他做对第(i)个题的概率就是(frac{min(a_i, a_{i + 1})}{a_i imes a_{i + 1}})也就是说,答案就应该是(sumfrac{1}{a_i^2}) ,就是(frac{a_i}{a_i^2})

    那么如果不相等呢?

    很显然,会有一些项变成(0),再仔细想一下的话就可以发现做对第(i)道题的概率是(min(a_i,a_{i+1}) cdot frac{1}{a_i cdot a_{i+1}})

    好的,由于权值是单位(1),所以这就做完了,对于(i=n)的情况特判即。

    但是这个式子可以继续化简,大概经历这么一个简单的过程:

    (egin{align}min(a_i,a_{i+1}) cdot frac{1}{a_i cdot a_{i+1}} &=min(a_i,a_{i+1}) cdot frac{1}{max(a_i,a_{i+1}) cdot min(a_i,a_{i+1})} \ &=frac{1}{max(a_i,a_{i+1}) }end{align})

    可以使常数小一点(233)

    ·$ 2$

    我们用一种玄学的思路思考,那么不妨(yy)一个结论(:) 一组选项中,选项数量少的一定可以不去管,所以概率为(frac{1}{max(a_i,a_{i+1}) })

    那这个结论为什么是对的呢?我们思考,其实选项数量少的选项在决策过程中,不会产生任何贡献,因为如果(a_i>a_{i+1}),那么我们即使在(a_{i+1})种情况里选对了,也不一定会在(a_i)情况里面选对;反之亦然。

    好的,猜结论是种技巧——(zhx)

    其实根本不用猜吧,这个题这么水

    #include <cstdio>
    #include <iostream>
    #define MAXN 10001000
    
    using namespace std ;
    double Ans = 0.0 ;
    long long N, A, B, C, Aa[MAXN], i ;
    
    int main(){
    	scanf("%d%d%d%d%d", &N, &A, &B, &C, Aa + 1);
    	for (i = 2 ; i <= N; ++ i)
    		Aa[i] = ((long long)Aa[i - 1] * A + B) % 100000001 ;  
    	for (i = 1; i <= N ; ++ i) Aa[i] = Aa[i] % C + 1 ;
    	for (i = 1; i < N ; ++ i) Ans += (double)1.0 / max(Aa[i], Aa[i + 1]) ;
    	Ans += (double)1.0 / max(Aa[N], Aa[1]) ;
    	printf("%.3lf", Ans) ;
    }
    

    (Task ~3~~) 期望( m{DP})

    其实期望(DP)虽然各大(OJ)上给的(Label)比较高,但实质上考察的就是比较简单的状态转移而已——无非就是转移过程中把概率的因素算在其中了。

    比如说一道小水题:

    ( m{color{red}{Description cdot 4}})

    (Link)

    某一天WJMZBMR在打osu

    我们来简化一下这个游戏的规则

    有nn次点击要做,成功了就是o,失败了就是x,分数是按combo计算的,连续(a)个combo就有(a imes a)分,combo就是极大的连续o

    比如ooxxxxooooxx,分数就是$ 2 imes 2 + 4 imes 4 = 4 +16=20$。

    Sevenkplus闲的慌就看他打了一盘,有些地方跟运气无关要么是o要么是x,有些地方o或者x各有50%的可能性,用?号来表示。

    比如oo?xx就是一个可能的输入。 那么WJMZBMR这场osu的期望得分是多少呢?

    比如oo?xx的话,?o的话就是oooxx$ => 9$,是x的话就是ooxxx (=> 4)

    期望自然就是((4+9)/2 =6.5)

    ( m{color{red}{Solution cdot 4}})

    其实是一个很简单的题,我们只需要思考状态是怎么转移的即可。

    很显然我们应该考虑多一个字符的时候,会对原序列产生什么新的影响。

    我们不妨记(dp_i)为期望的得分(答案),(Len_i)表示到(i)位置为止的连续的o的期望长度。

    如果新的是o,那么我们很简单的可以得出来:

    (Len_i = Len_{i - 1} + 1,) 这个结论是平凡的。那么对于(dp_i),我们考虑,由于本题的权值是平方和,所以说我们的(dp_i = dp_{i - 1} + 2 * Len_{i - 1} * 1 + 1)(完全平方公式)

    那么如果新的是x,那么我们的状态就要“清零”:

    [dp_i = dp_{i -1}, Len_i = 0 ]

    如果新的是?,那么我们只需要把第一个方程乘上0.5即可

    (Len_i = frac{Len_{i - 1} + 1}{2},dp_i = dp_{i - 1} + Len_{i - 1} + 0.5)

    #include <cstdio>
    #include <iostream>
    #define MAXN 10001000
    
    using namespace std ;
    double Ans = 0.0 ;
    long long N, A, B, C, Aa[MAXN], i ;
    
    int main(){
    	scanf("%d%d%d%d%d", &N, &A, &B, &C, Aa + 1);
    	for (i = 2 ; i <= N; ++ i)
    		Aa[i] = ((long long)Aa[i - 1] * A + B) % 100000001 ;  
    	for (i = 1; i <= N ; ++ i) Aa[i] = Aa[i] % C + 1 ;
    	for (i = 1; i < N ; ++ i) Ans += (double)1.0 / max(Aa[i], Aa[i + 1]) ;
    	Ans += (double)1.0 / max(Aa[N], Aa[1]) ;
    	printf("%.3lf", Ans) ;
    }
    

    ( m{color{red}{Description cdot 5}})

    (Link)

    换教室……童年噩梦233

    简化题目,其实就是说,给定一张无向图,我们一开始在每个时刻都必须要去一个点,或者通过一定概率去另外一个点(有次数限制),最后求最短路。

    (n,m leq 2000, v leq 300)

    ( m{color{red}{Solution cdot 5}})

    首先考虑,无论怎么移动,我们必须要走最短路,这是显然的,观察数据范围,可知要用弗洛伊德。

    我们接着考虑,我们的状态一定要是二维的——(dp_{i,j})表示在时刻(i)时,换了(j)次教室的最短距离期望。但是这样会产生状态表述不清的嫌疑——所以我们再加一维,(dp_{i, j, 0/1})表示表示在时刻(i)时,换了(j)次教室,本次换/不换的最短距离期望。

    所以我们的转移呢?我们考虑只能从上一个时刻转移过来,那么一共由乘法原理推出,有四种转移方式:

    • 这次换,上一次不换;
    • 这次不换,上一次不换;
    • 这次换,上一次换;
    • 这次不换,上一次换;

    那么转移的时候,换的概率自然是(p_i),而不换的概率我们可以形式化地记为((1- p_i)),那么转移就比较简单了~

    值得注意的是,我们求的是期望,所以对于除了两次都不换的情况其他所有情况,我们都要把所有可能都考虑在内。那么转移时的方程大概是这样:

    for(int i=2;i<=n;i++){
          for(int j=0;j<=min(m,i);j++)
           {                     
              dp[i][j][0]=min(dp[i-1][j][0] + f[c[i-1]][c[i]], dp[i-1][j][1] + f[d[i-1]][c[i]] * p[i-1] + f[c[i-1]][c[i]] * (1-p[i-1]));
              if(!j) continue ;
              dp[i][j][1]=min(dp[i - 1][j - 1][0] + f[c[i-1]][d[i]] * p[i] + f[c[i - 1]][c[i]] * (1 - p[i]), 
              dp[i - 1][j - 1][1] + f[c[i-1]][c[i]] *(1 - p[i - 1]) * (1 - p[i]) + f[c[i-1]][d[i]] * (1 - p[i-1]) * p[i] + f[d[i-1]][c[i]] * (1 - p[i]) * p[i - 1] + f[d[i-1]][d[i]] * p[i-1] * p[i]) ; //四种情况
           }   
        }      
    

    虽然很麻烦的样子,但是思路是很清晰的233

    不知道我什么时候可以有这样清晰的头脑啊233

    ( m{Code})

    #include<iostream>
    #include<cstdio>
    #define qwq register 
    #define MAXN 3010
    
    using namespace std;
    
    int a[MAXN][MAXN], c[MAXN], d[MAXN] ;
    double p[MAXN], f[MAXN][MAXN], dp[MAXN][MAXN][2] ;
    
    inline double min(double a,double b){ return a < b ? a : b ;}
    inline int qread(){
        int  k = 0 ; char c = getchar() ;
        while(!isdigit(c)) c = getchar() ;
     	while(isdigit(c)) k = (k<<1)+(k<<3)+c-48, c = getchar();
        return k ;
    }
    inline double qread_double(){
        double k=0;char c=getchar();
        while(!isdigit(c))c=getchar();
        while(isdigit(c))k=k*10+(c-48),c=getchar();
        if(c=='.'){
            double base=0.1;c=getchar();
            while(isdigit(c))k=k+(c-48)*base,base/10,c=getchar();
        }
        return k ;
    }
    int main(){
        int n,m,v,e,a1,b1,c1;
        cin >> n >> m >> v >> e ;
        for(qwq int i = 1 ; i <= n ; ++ i) c[i]=qread();
        for(qwq int i = 1 ; i <= n ; ++ i) d[i]=qread();
        for(qwq int i = 1 ; i <= n ; ++ i) cin>>p[i] ;
        
        for(qwq int i=1;i<=v;i++)
            for(qwq int j=1;j<i;j++)
                f[i][j] = f[j][i] = 1926081700 ;
        for(qwq int i=1;i<=e;i++){
        	a1=qread(),b1=qread(),c1=qread();
            f[a1][b1] = f[b1][a1] = min(f[a1][b1],c1) ;
        }
        for(qwq int k=1;k<=v;k++)
          for(qwq int i=1;i<=v;i++)
            for(qwq int j=1;j<i;j++)
              if(f[i][k]+f[k][j]<f[i][j])
                 f[i][j]=f[j][i]=f[i][k]+f[k][j];
                 
        for(qwq int i=1;i<=n;i++)
            for(qwq int j=0;j<=m;j++)
                dp[i][j][0]=dp[i][j][1]=999999999;
         
        dp[1][0][0]=dp[1][1][1]=0;
        for(qwq int i=2;i<=n;i++){
         double add1=f[c[i-1]][c[i]];
          for(qwq int j=0;j<=min(m,i);j++)
           {                     
              dp[i][j][0]=min(dp[i-1][j][0]+add1,dp[i-1][j][1]+f[d[i-1]][c[i]]*p[i-1]+f[c[i-1]][c[i]]*(1-p[i-1]));
              if(j!=0)
              dp[i][j][1]=min(dp[i-1][j-1][0]+f[c[i-1]][d[i]]*p[i]+f[c[i-1]][c[i]]*(1-p[i]),dp[i-1][j-1][1]+f[c[i-1]][c[i]]*(1-p[i-1])*(1-p[i])+f[c[i-1]][d[i]]*(1-p[i-1])*p[i]+f[d[i-1]][c[i]]*(1-p[i])*p[i-1]+f[d[i-1]][d[i]]*p[i-1]*p[i]);
           }   
        }          
                                   
        double hahaha=9999999999;
        for(int i=0;i<=m;i++){
        hahaha=min(dp[n][i][0],min(dp[n][i][1],hahaha));}
        printf("%.2lf",hahaha);
    }
    

    ( m{Task ~5}) 不是很可做的期望题目

    这个这个……我好像只做过一道不可做的……

    ( m{color{red}{Description cdot 6}})

    (Link)

    康娜的线段树……是个好题……

    大概题意就是,康娜有一棵线段树,支持区间加,但是她的区间加并不是打标记的区间加,而是暴力区间加:

    	for(int i=l;i<=r;i++)T.change(1,1,n,i,addv);
    

    看上去不是很耐撕,但是康娜想知道,如果每次在线段树区间加操作做完后,从根节点开始等概率的选择一个子节点进入,直到进入叶子结点为止,将一路经过的节点权值累加,最后能得到的期望值是多少?

    ( m{color{red}{Solution cdot 6}})

    此题思路借鉴(Link),在此表示敬意。

    比较难的一道题了

    每个叶子结点对答案的贡献值,其实就是从根结点到达这个叶子结点路径经过的所有点权值和,乘上(frac{1}{2^{dep}})。这是比较显然的。

    那么我们求个和即可,那怎么求和呢?我们考虑利用线段树的性质:算完再除

    我们考虑一个小的转化:$$sum frac{v_i}{2^{dep_i}}-> 2^D cdot sum v_i cdot {2^{D - {dep_i}}} ext{【1】}$$

    我们发现一个叶节点值增加(x),答案就会增加(x imes sum_{i=1}^{dep}{frac{1}{2^{i-1}}}), 发现这个东西就是个等比数列,很容易地化简为(1 + 1 - frac{1}{2^{dep-1}} = 2 - frac{1}{2^{dep-1}} = frac{2^{dep}-1}{2^{dep-1}})。所以我们最后再前缀和一下就好。

    所以就可以最后再除。

    而我们知道,单点修改时,我们是对一条链进行操作。所以这个东西可以直接前缀和,前缀和出链长,维护一个永久(Ans)记录答案即可。

    代码:

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #define MAXN 3000010
    #define ll long long
    #define ls(x) x << 1
    #define rs(x) x << 1 | 1
    #define mid ((l + r) >> 1)
    
    using namespace std ;
    ll qwq, L, R, D, Ans, Len, N, M, i ;
    ll base[MAXN], dep[MAXN << 2], High, T[MAXN << 2], S[MAXN] ;
    
    inline ll qr(){
        ll k = 0 , f = 1 ; char c = getchar() ;
        while (c < '0' || c > '9') {if (c == '-') f = -1 ; c = getchar() ;}
        while (c <= '9' && c >= '0') k = (k << 1) + (k << 3) + c - 48, c = getchar() ;
        return k * f ;
    }
    inline void _build(ll rt, ll l, ll r, ll deep){
        if(l == r){
            dep[l] = deep ; T[rt] = base[l] ;
            High = max(High, dep[l]) ; return ;
        }
        _build(ls(rt), l, mid, deep + 1) ;
        _build(rs(rt), mid + 1, r, deep + 1) ;
        T[rt] = T[ls(rt)] + T[rs(rt)] ;
    }
    inline ll query(ll rt, ll l, ll r, ll deep, ll Tot){
        if(l == r) return 1LL * ((T[rt] + Tot) * (1LL << deep)) ;
        else return query(ls(rt), l, mid, deep - 1, Tot + T[rt])
                  + query(rs(rt), mid + 1, r, deep - 1, Tot + T[rt]) ; // 简化运算,最后同时除以。 
    }
    int main(){
        cin >> N >> M >> qwq ;
        for(i = 1 ; i <= N ; ++ i) base[i] = qr() ;
        _build(1, 1, N, 1) ;
        Ans += query(1, 1, N, High - 1, 0) ; Len = 1LL << (High - 1) ;
        ll _Temp = __gcd(Len, qwq) ; qwq /= _Temp, Len /= _Temp ;
        for(i = 1 ; i <= N ; ++ i)
            S[i] = S[i - 1] + (1 << dep[i]) - 1 + (dep[i] != High) * ((1 << dep[i]) - 1) ; 
        //此处为预处理深度,因为是完全二叉树,所以要么深度等于树高 ,要么深度等于树高减一 
        //但其实,我们预处理的是有关于我们的优化【1】式的。所以我们在每一层还要再乘上一个$2^{dep}$。
        for(i = 1 ; i <= M ; ++ i){
            L = qr(), R = qr(), D = qr(), Ans += (S[R] - S[L - 1]) * D, printf("%lld
    ", Ans / Len * qwq ) ;
        return 0 ;
    }
    
    

    最后撒花花吧~~

  • 相关阅读:
    整数划分《递归法》
    hdu 1224 Free DIY Tour
    HTTP Response Status Code HTTP响应代码中文详解
    Webserive学习站点
    页面的回传与回调
    JS中apply和call函数的运用
    SOAP协议详解
    JS在firefox和IE下差异及解决方案
    关于路径的问题
    .NET中IDisposable接口的基本使用 (转)
  • 原文地址:https://www.cnblogs.com/pks-t/p/9515364.html
Copyright © 2020-2023  润新知