概率期望
引:
一个简单问题:
在一条数轴上, 从0点开始, 每一秒有一般的几率向正方向走一个单位, 有一半几率停着不动
问在(0,1)的概率与期望秒数
如果是停留在(0), 可以$f[0] = (f[0] + 1)*0.5 (, 所以)f[0]=1$
也可以(sum^{infty}(frac{1}{2})^i)
但是发现算(1)的时候, 在(n)秒中, 可以随意选择其中一秒走一步, 其他停住, 这样答案算起会很大((sum^{infty}(frac{1}{2})^i*i^2))
显然错了, 因为到达自己之后仍然可以停留, 那么这个事件的定义就不明确了(既不是停留, 又不是到达)
如果是定义第一次到这个点的话, 就不能算自己到自己的这个事件, 这种傻逼问题之后可能又会碰到
基本定义
- 概率空间
- (Omega)
- 基本事件
- (elementray event, 单元事件, 不可再分): (omega)
- 事件
- (event): (A)
(omega in Omega, omega in A, A subseteq Omega)
- 随机变量
- (定义在(omega)上): (S), 如(S(omega))定义为两个骰子点数和, 再例如"(事件S(omega)=7的概率)"
- 独立的随机变量
- 如果同一个(Omega)中的两个随机变量(X,Y), 有
则称(X,Y)为独立的随机变量(这是定义)
套路
考虑每一步对期望的贡献
有 (n) 个点, 一开始全白, 每次某个概率选一个点染成黑色, 求期望全染黑的步数?
有期望的定义式可得: (Ans = sum_{i > 0} i cdot Pr_{第 i 步恰好全黑})
考虑什么时候会走这个 第$i步 , 即第 (i) 步时未全染黑的概率, 因为只要这时未全染黑, 后面无论何时染黑(后面情况加起来概率为1), 都需要走这一步, 所以这一步的贡献 = 概率
套路: 上述期望 = (sum_{i > 0} Pr_{第 i 步没有全黑的概率})
简单题
LightOJ1027 A Dangerous Maze <简单>
假迷宫, 假如门的代价是正数, 就可以花这个代价出去, 否则要花绝对值的代价, 留在原地
问出去的期望
(E = sum frac{1}{n}*cost_i+sum frac{1}{n}*(cost_i + E))
化简即可
int T = in();
for (int t = 1; t <= T; ++ t)
{
n = in();
int sumx = 0, sumy = 0, cnty = 0, g = 0;
for (int i = 1; i <= n; ++ i)
{
int x = in();
if (x > 0) sumx += x; else sumy -= x, ++ cnty;
}
if (sumx == 0) { printf("Case %d: inf
", t); continue; }
g = gcd(sumx + sumy, n - cnty);
printf("Case %d: %d/%d
", t, (sumx + sumy) / g, (n - cnty) / g);
}
LightOJ1030 Discovering Gold <简单>
金矿有(n)个, 每个金矿有一定收益, 一开始在(1), 投骰子([1,6]), 假如走这些步数后(>n)就一直投直到(le n), 求到(n)时期望收益
memset(f, 0, sizeof f);
for (int i = n - 1; i >= 1; -- i)
{
int p = min(6, n - i);
for (int j = 1; j <= p; ++ j)
f[i] += (f[i + j] + val[i + j]) / (double)p;
}
ans = f[1] + val[1];
LightOJ1038 Race to 1 Again <预处理?!>
(T(le 10))组数据, 每次给出(N(le 10^5))
每个数有相同几率跳到他的因数(包括自己), 求跳到(1)的期望步数
设(cnt)为(i)的因数个数, (f[i]=frac{1}{cnt}*sum f[j]+1)
((1-frac{1}{cnt})f[i]=frac{1}{cnt} sum ^{!=i}f[j]+1)
(f[i]=(sum^{!=i}f[j]+cnt) / (cnt - 1))
所有(10^5)以内都可以一遍(DP)预处理
int T, n;
double dp[MAXN];
void init()
{
for (int i = 2; i <= 100000; ++ i)
{
double sum = 0, cnt = 0;
for (int j = 1; j * j <= i; ++ j)
{
if (i % j != 0) continue;
sum += dp[j], cnt += 1;
if (i / j != j) sum += dp[i / j], cnt += 1;
}
dp[i] = (sum + cnt) / (cnt - 1);
}
}
int main()
{
init();
T = in();
for (int i = 1; i <= T; ++ i)
{
n = in();
printf("Case %d: %.6lf
", i, dp[n]);
}
return 0;
}
LightOJ1248 Dice (III) <期望初步>
问(n)面的均匀的骰子每一面都投到的期望次数
(E_i)表示已经有(i)个不同的面时的期望
那么(E_i = 1 + frac{i}{n}E[i]+frac{n-i}{n}E[i+1])
意思是说: 现在投一次, 有(i/n)的几率投到出现过的, 那么又回到(i)这个点, 还有剩下几率投到新的面
LightOJ1104 Birthday Paradox <正难则反>
求当一年为(n(le 10^5))天时, 至少要多少人, 才能满足他们中至少有两人生日相同的概率(ge 0.5)
Sol:
竟然不会....
至少有两人相同可以转化成(1.0-)所有人都不相同的概率
BZOJ2752: HAOI2012高速公路 <假期望>
有(n(le 10^5))个点, 相邻两点之间有边权, 初始是(0)
有(M(le 10^5))个询问/操作, 操作是将([l, r])之间的边权都加上一个值, 查询是问在([l,r])中选两个不同的点, 他们之间边权和的期望
Sol:
假期望, 这是所有选择的边权和的和, 让它取除以方案数( binom{r-l+1}{2})
中等
LightOJ1151 Snakes and Ladders <高斯消元>
自以为思路很正确, 实则漏洞百出
在(1*100)的棋盘上, 每个点至多有一个单向传送, 起点和终点不会有传送
可以扔一个(6)面的骰子, 假设投到(x), 然后花一步跳到往后数(x)个格子上, 如果超过(100)就重新投(但是要花费步数)
问期望几次从(1)到达(100)
Sol:
因为传送是xjb乱传的, 不能递推, 用高斯消元
考虑期望方程(读清题意!!!)
能传送和不能传送的点是不同的, 能传送的点根本就不能有任何操作直接传送到那个点!!!
能传送:
不能传送:
可以根据这些列出方程
int n, to[MAXN];
DB a[MAXN][MAXN];
DB ansx[MAXN];
void check(int n)
{
puts("------");
for (int i = 1; i <= n; ++ i)
{
for (int j = 1; j <= n + 1; ++ j)
printf("%.1f ", a[i][j]);
puts("");
}
puts("------");
}
bool nosol, mulsol;
void Gauss(int n)
{
int r = 1, c = 1, maxr = 0;
for (; r <= n && c <= n; ++ r, ++ c)
{
maxr = r;
for (int i = r + 1; i <= n; ++ i)
if (fabs(a[i][c]) - fabs(a[maxr][c]) > EPS) maxr = i;
if (maxr != r)
for (int i = 1; i <= n + 1; ++ i) // n + 1 !!!
swap(a[r][i], a[maxr][i]);
if (fabs(a[r][c]) < EPS) { -- r; continue; }
for (int i = r + 1; i <= n; ++ i)
{
double k = a[i][c] / a[r][c];
for (int j = c; j <= n + 1; ++ j)
a[i][j] = a[i][j] - k * a[r][j];
}
}
-- r, -- c;
for (int i = n; i >= 1; -- i)
{
int all0 = 1;
for (int j = i; j <= n; ++ j) all0 &= (fabs(a[i][j]) < EPS);
if (all0)
{
if (fabs(a[i][n + 1]) > EPS) nosol = true;
else mulsol = true;
}
else
{
for (int j = i + 1; j <= n; ++ j)
a[i][n + 1] -= ansx[j] * a[i][j];
ansx[i] = a[i][n + 1] / a[i][i];
}
}
}
int main()
{
int T = in();
for (int t = 1; t <= T; ++ t)
{
memset(ansx, 0, sizeof ansx);
memset(a, 0, sizeof a);
memset(to, 0, sizeof to);
n = in();
for (int i = 1; i <= n; ++ i)
{
int x = in(), y = in();
to[x] = y;
a[x][x] = 1; a[x][y] = -1;
}
for (int i = 1; i <= 100 - 1; ++ i)
{
if (to[i]) continue;
a[i][i] = min(6, 100 - i);
for (int x = 1; x <= min(6, 100 - i); ++ x)
a[i][i + x] = -1;
a[i][101] = 6;
}
a[100][100] = 1;
Gauss(100);
printf("Case %d: %.7f
", t, ansx[1]);
}
return 0;
}
LightOJ 1079 Just another Robbery <正难则反>
(100) 组数据, 每组(le 100)个银行, 每个银行有一个被抓住的几率, 和成功时获得的收益((le 100))
问当总的被抓的几率(le lim)时能获得的最大收益
其实算没被抓的几率就可以了
也可以像我一样傻逼的算被抓的几率:
- 证明以不同顺序抢同一些银行被抓的几率相同
- 列一下式子, 发现是容斥, 证毕
- 证明抢一组银行的被抓/没被抓的几率可以整合, 视作一个合体银行
- 被抓=(a+(1-a)*b+(1-a)*(1-b)*c=P_{ab}+(1-P_{ab})*c)
- 展开发现一样, 也是容斥, 或者感性理解也行
好了, 滚动(一开始忘记)背包, 下标是收益
for (int k = 1; k <= T; ++ k)
{
sum = ans = tot = 0;
scanf("%lf%d", &lim, &n);
for (int i = 0; i <= 10000; ++ i) f[i] = 1; f[0] = 0;
for (int i = 1; i <= n; ++ i)
{
int x; double y;
scanf("%d%lf", &x, &y);
if (y <= lim)
{
++ tot;
c[tot] = y, v[tot] = x;
sum += x;
}
}
for (int i = 1; i <= tot; ++ i)
for (int j = sum; j >= v[i]; -- j)
f[j] = min(f[j], f[j - v[i]] + (1 - f[j - v[i]]) * c[i]);
for (int i = sum; i >= 0; -- i)
if (f[i] <= lim) { ans = i; break; }
printf("Case %d: %d
", k, ans);
}
XJOI20190725T3游戏 <假博弈真期望>
Alice和Bob两个人正在玩一个游戏,游戏有很多种任务,难度为p的任务(p是正整数),有1/(2p)的概率完成并得到2(p-1)分,如果完成不了,得0分。一开始每人都是0分,从Alice开始轮流做任务,她可以选择任意一个任务来做;而Bob只会做难度为1的任务。只要其中有一个人达到n分,即算作那个人胜利。求Alice采取最优策略的情况下获胜的概率。
输入格式:
一个正整数n,含义如题目所述。
输出格式:
一个数,表示Alice获胜的概率,保留6位小数。
样例输入:
1
样例输出:
0.666667
数据范围:
对于30%的数据,n≤10
对于100%的数据,n≤500
乍一看以为是博弈论
没想到是枚举所有状态进行概率(DP)
设(DP[i][j])表示(A, B)分别为(i,j)时到达赢的状态的概率, 倒着推
那就枚举所有的任务来转移, 取其中最大的转移
把(DP[x][y]|xge n ~and~yge n)全部设成(1.0), 这样就不用考虑边界要不要乘(B)的概率的问题(想想为什么?)
for (int i = 1; i <= 10; ++ i)
val[i] = 1 << (i - 1), p[i] = 1 << i;
n = in();
for (int j = n; j <= 1024; ++ j)
for (int i = 0; i <= n; ++ i)
dp[j][i] = 1.0;
for (int i = n - 1; i >= 0; -- i)
{
for (int j = n - 1; j >= 0; -- j)
{
double tmp = 0.0;
for (int x = 1; x <= 10; ++ x)
{
tmp = max(tmp, ((dp[i + val[x]][j + 1] + dp[i + val[x]][j]) * 1.0 / (double)p[x] * 0.5
+ dp[i][j + 1] * (1.0 - 1.0 / (double)p[x]) * 0.5)
/ (1.0 - (1.0 - 1.0 / (double)p[x]) * 0.5));
}
dp[i][j] = tmp;
}
}
printf("%.6f
", dp[0][0]);
难题
LightOJ - 1395 A Dangerous Maze (II) <!!!SO HARD!!!>
好题
题意概述:
你在一个迷宫里(假), 起点有(n(le 100))个门
走一个门有个代价(x_i (1le abs(x_i)le10000)), 如果是正数, 就可以花费这个代价离开迷宫, 如果是负数, 花费代价的绝对值, 仍然停留在原地
但是你只能记住最后访问的(k(le 100))个门, 你不会走这些门(因为你还待在迷宫里, 意味着那些门不能出去), 而其他门是随机选择的
问期望离开迷宫的代价
Sample Input
4
2 0
10 10
2 0
10 -10
3 1
10 -10 -20
3 2
10 -10 -20
Sample Output
Case 1: 10
Case 2: 20.000
Case 3: 30.0000000000
Case 4: 25.0000000000
Sol:
先考虑没有记忆的情况, 即每次都随机选门, 设能出去的门个数为(x), 不能出去的为(y)
则期望
那么有记忆呢?
- 首先(k = min(n,k))
假设当前记住的状态为(now), 记住了(i)个
这是候选项是(n - i), 不能出去的门还剩(y-i)
(k=i)时略有不同
可以发现因为这些式子加好后面枚举了剩下的门, 所以这些期望方程大概形成了树型的关系
但是每个状态不同, 剩下的门也不同, 怎么办
可以把树上同一层的式子(E[i])加起来, 把(E[i])作为一个整体来算
感性发现因该是
倒着推就行了, 理性证明见这里
int T, n, m;
int x, y;
double sumx, sumy;
double p[106];
int main()
{
scanf("%d", &T);
for (int tnum = 1; tnum <= T; ++ tnum)
{
x = y = 0; sumx = sumy = 0.0;
n = in(), m = in();
p[0] = 0.0;
for (int i = 1; i <= n; ++ i)
{
p[i] = 0.0;
int tmp = in();
if (tmp >= 0) ++ x, sumx += tmp;
else ++ y, sumy -= tmp;
}
if (y == 0) { printf("Case %d: %.9f
", tnum, sumx / (double)x); continue; }
if (x == 0) { printf("Case %d: -1
", tnum); continue; }
m = min(m, y);
p[m] = (double)(sumx + (y - m) * sumy / (double)y) / (double)(n - m) / (1.0 - (double)(y - m) / (double)(n - m)) ;
for (int i = m - 1; i >= 0; -- i)
p[i] = (sumx + (double)(y - i) * (sumy / (double)y + p[i + 1])) / (double)(n - i);
printf("Case %d: %.9f
", tnum, p[0]);
}
return 0;
}
BZOJ1076: [SCOI2008]奖励关 <较难>
发放(k(le 100))轮宝物, 每次随机发放(n(le 15))个宝物其中一个, 即每次对于所有宝物几率都为(frac{1}{n})
宝物的价值可能为负((in [-10^6, 10^6])), 并且拿取宝物有先决条件, 要满足当前已经有某些宝物
你可以决策收不收取这轮的宝物(不满足先决条件时一定不能收取), 求在所有可能的情况下最优决策得到的平均价值
Sol:
最有决策的平均价值是什么意思?
即对于当前这个状态, 有(n)种转移, 每种转移又有取与不取两种决策, 最优决策当然是取收益大的那个决策, 而平均价值则是把每个转移两个里较大的那个加起来, 再除以(n)
要怎么知道转移后收不收取那个更优呢? 想到了倒着推
(f[i][sta])表示到第(i)轮时, 每个宝物是否拥有的情况为(sta), 到末状态的最优决策的平均价值
//BZOJ MLE返回TLE
scanf("%d%d", &m, &n);
for (int i = 1; i <= n; ++ i)
{
scanf("%d", &val[i]);
while (true)
{
int x; scanf("%d", &x);
if (!x) break;
prem[i] |= (1 << (x - 1));
}
}
for (int i = m - 1; i >= 0; -- i)
{
for (int now = 0; now < (1 << n); ++ now)
{
for (int j = 1; j <= n; ++ j)
{
if ((now | prem[j]) != now) f[i][now] += f[i + 1][now];
else f[i][now] += max(f[i + 1][now | (1 << (j - 1))] + val[j], f[i + 1][now]);
}
f[i][now] /= (double)n;
}
}
printf("%.6f
", f[0][0]);
BZOJ1426 收集邮票 <辅助期望>
神题:
有n种不同的邮票,每次只能买一张,并且买到的邮票究竟是n种邮票中的哪一种是等概率的,概率均为1/n,购买第k张邮票需要支付k元钱。
求得到所有种类的邮票需要花费的钱数目的期望。
input
3
output
21.25
Sol:
设(f[i])为有了(i)张时, 期望再买几次能凑齐, (g[i])为...时期望代价
(f[i]=frac{i}{n}f[i]+frac{n-i}{n}f[i+1]+1)
(g[i]=frac{i}{n}(g[i]+f[i]+1)+frac{n-i}{n}(g[i+1]+f[i+1]+1))
即把当前花费当做(1), 其他一次增长, 因为反正都是花费这些数, 知道总共花费几次, 花费的顺序是无所谓的
BZOJ2830: SHOI2012 随机树 <树上子问题>
初始树上只有一个点, 定义根节点深度为(0)
每次操作随机选择一个叶子, 给他增加两个儿子
求有(n)个叶子时, 叶子深度的平均值的期望, 和树深度的期望
Sol:
乍一看以为第一问比较难, 其实他比较简单
第二问:
显然是DP, 这种题在于如何选择DP的状态, 以及选择算期望/算概率, 还有借用辅助的概率/期望, 如果定义好了DP, 那么推一下是不难的
一开始想了几种一维的DP(每次要么选择最深点, 深度++, 否则深度不变), 还想过借用算大小为(i)的树深度为(j)的叶子个数的期望值来推, 都不行
观察一下这道题, 期望的深度是实数, 而对于每一种可能他的深度是整数, 而概率是实数, 不妨计算每一个深度出现的概率
如果想到左右子树分别是一个子问题的话, 这道题目就好做了, 而不是把所有叶子节点根据深度分类, 这样并不能简化问题
(f[i][j])表示叶子有(i)个时, 深度是(j)的概率, 那么枚举左右字数叶子个数以及深度, 再加一个前缀和优化即可
double g[MAXN];
double f[MAXN][MAXN], p[MAXN][MAXN], t[MAXN][MAXN];
double solve(int n)
{
p[1][1] = 1;
for (int i = 3; i <= n; ++ i)
{
for (int j = 1; j < i; ++ j)
{
if (j > 1) p[j][i - j] += p[j - 1][i - j] * (DB)(j - 1);
if (i - j > 1) p[j][i - j] += p[j][i - j - 1] * (DB)(i - j - 1);
p[j][i - j] /= (double)(i - 1);
}
}
f[1][0] = t[1][0] = 1.0; f[2][1] = 1.0;
for (int i = 1; i <= n; ++ i) t[1][i] = t[2][i] = 1.0;
for (int i = 3; i <= n; ++ i)
{
for (int j = 1; j < i; ++ j)
{
for (int ls = 1; ls < i; ++ ls)
{
if (j > 1)
f[i][j] += (f[ls][j - 1] * t[i - ls][j - 2] + f[i - ls][j - 1] * t[ls][j - 2]) * p[ls][i - ls];
f[i][j] += f[ls][j - 1] * f[i - ls][j - 1] * p[ls][i - ls];
}
t[i][j] = t[i][j - 1] + f[i][j];
}
for (int j = i; j <= n; ++ j) t[i][j] = t[i][j - 1] + f[i][j];
}
double ret = 0;
for (int i = 1; i < n; ++ i) ret += 1.0 * i * f[n][i];
return ret;
}
int main()
{
m = in(); n = in();
for (int i = 2; i <= n; ++ i) g[i] = g[i - 1] + 2 / (1.0 * i);
if (m == 1) printf("%.6f
", g[n]);
else printf("%.6f
", solve(n));
return 0;
}
Codeforces 235B Let's Play Osu!
有长度为(n)的(01)串, 第(i)个位置有(pi)的几率是(1), 否则为(0)
一个串的值为每一段最长连续的(1)的长度的平方的和
求期望值
待
20191002 Cyanic Contest Problem C. Fibonacci’s Nightmare <推式子>
定义一个随机线性递推序列(即random linear recursive sequence, RLRS)是一个由如下方式生成的非负整数序列。一开始,令(a_0= 1)。然后对于从(1)开始的每一个(n),独立且等概率在([0, n−1])中随机(k)个非负整数(i_1, i_2,···, i_k,)令(a_n=∑^k_{j=1}a_{i_j})。接下来是一个例子。令(k= 2),那么有(a_1=a_0+a_0= 2),之后,(a_2)的值等概率从(a_0+a_0, a_0+a_1, a_1+a_0, a_1+a_1)中选择。小C解决了(k= 2)时(a^2_n)的数学期望。她想问你对于更大的(k),(E(a^k_n))在模(998244353)下的值
这里的(E(a^k_n))指((a_n)^k)的期望
Sol:
对于(k=2)
设(f(n) = E((a_n)^2))
发现还需要一个(g(n)=E(sum_{i=0}^{n}a_i^2),t(n)=E(sum_{i=0}^{n}sum_{j=0}^{n}a_ia_j))
即
而
(t(n))就比较麻烦了
NOIP2016换教室<期望DP + 神奇状态?多个状态加一起>
有(n)个课程, 可以申请(m)次换教室, 第(i)个课程如果申请后成功则去(d_i), 失败或不申请都是在(c_i), 概率给定
一次性申请, 确定教室后依次走最短路到下一个教室(给图)
问怎样申请期望走的路最短, 输出这个值
Sol:
设(f[i][j][0/1])表示到第(i)个教室时申请了(j)次, 这次是否申请, 的最小期望
注意不是是否换教室, 而是是否申请, 因为题目要求一次性申请, 前者可能会有一步一步决策的问题; 而后者是一次选择选不选, 所以是正确的
其实选了的DP值是包含两个状态的, 转移时一起考虑其实跟分开考虑是一样的(画分支想想为什么)
总结
- 子问题
- 前缀和优化
- 递推式-->通向式
- 式子化简
- 设置DP的状态, 选择概率/期望
- 遇到高维的期望应当从高维开始推求出需要哪些低维的期望, 而不是把低维推向高维
- 严谨写转移方程