学记笔记 $ 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 ;
}

最后撒花花吧~~

原文地址:https://www.cnblogs.com/pks-t/p/9515364.html