最近泛做了期望的相关题目,大概(Luogu)上提供的比较简单的题都做了吧(233)
好吧其实是好几天之前做的了,不过因为太颓废一直没有整理……
(Task1) 期望的定义
在概率论和统计学中,数学期望((mean))(或均值,亦简称期望)是试验中每次可能结果的概率乘以其结果的总和,是最基本的数学特征之一。它反映随机变量平均取值的大小。
需要注意的是,期望值并不一定等同于常识中的“期望”——“期望值”也许与每一个结果都不相等。期望值是该变量输出值的平均数。期望值并不一定包含于变量的输出值集合里。
大数定律规定,随着重复次数接近无穷大,数值的算术平均值几乎肯定地收敛于期望值。
——(baidu)百科
(hhh)其实意思就是概率的加权(平均值),此处需要注意,这个平均值是逻辑上的而并非运算上的。
(Task2) 期望的水题们
( m{color{red}{Description cdot 1}})
现在帕琪有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}} $
给出一个有向无环图,起点为(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}})
好的,这个题不是那么的水,是一个不错的题。
$ 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}})
某一天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
,那么我们的状态就要“清零”:
如果新的是?
,那么我们只需要把第一个方程乘上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}})
换教室……童年噩梦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}})
康娜的线段树……是个好题……
大概题意就是,康娜有一棵线段树,支持区间加,但是她的区间加并不是打标记的区间加,而是暴力区间加:
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 ;
}
最后撒花花吧~~