概率和期望
概率和期望
期望
常见技巧与知识
- 如果当前步数通往下一步时,有 \(p\) 的概率原地打转,则走到下一步的期望步数为 \(\frac{1}{1-p}\)
- 如果在进行某个操作时达到要求则停止,求期望步数,则可设达到要求后不停止,但不耗步数,保持问题的对称性
- 求步数的期望:\(ans=E[\text{步数}]=\sum_{i=0}^{\infty}Pr[\text{在}i\text{步后不满足要求}]\)
- 套路设计状态:设 \(f(i)\) 为已经走了 \(i\) 步时,走完的期望步数
一些公式:
条件期望:如果 \(A\) 不发生时,随机变量 \(X\) 的值取 \(0\),那么 \(E=E(X|A)P(A)\)
全期望公式:如果一组事件 \(C_{1\sim n}\) 满足它们之间恰好有一个会发生,即 \(\sum_{i=1}^n P(C_i)=1\),那么
这在 DP 中很常见
特例:\(E(A)=E(A|B)P(B)+E(A|\overline B)P(\overline B)\)
如果两个变量 \(X,Y\) 独立,即其结果互不影响,\(P((X=a)\cap(Y=b))=P(X=a)P(Y=b)\),那么 \(E(XY)=E(X)E(Y)\)
这个时候期望才能乘
注意:\(X\) 只有是常数时才和自己独立,独立性不传递,即 \(X,Y\),\(Y,Z\) 分别互相独立,但 \(X,Z\) 不一定互相独立
期望逆推
现在我还不是很确定为什么期望倒推,有两种说法我觉得都有道理:一是这种期望题末状态未知,但初状态已定,有可能某些末状态根本到不了,顺推的话还要计算到达它的概率,逆推比较方便;二是有人说倒推是为了保证转移时各种情况的概率之和为 \(1\),保证正确性。可能两种原因都有?
T1:P4550 收集邮票
因为邮票的花费与次数相关——要什么转什么,还要转期望次数
套路设计状态:\(f[i]\) 为已经买到i张邮票,买全n张的期望次数
分2种情况讨论:
-
下次不幸买到已有的i张,概率为 \(\frac i n\),还要再用 \(f[i]\) 的次数买
-
幸运的买了没有的 \(n-i\) 张,概率为 \(\frac{n - i} n\),用 \(f[i+1]\) 的次数即可
\(f[i] = \frac i n \times f[i] + \frac{n - i} n \times f[i + 1] + 1\) (本次花了 \(1\) 次买,\(+1\))
那钱数呢?买 \(k\) 次总钱数为 \(\frac {(k + 1) \times k} 2\),但期望不能这样算
套路设计状态:\(g[i]\) 为已经买到 \(i\) 张邮票,买全 \(n\) 张的期望总钱数
但我们不清楚已经买 \(i\) 张的总次数,只知道后面的次数 \(f[i]\)
那就把题改一下:购买倒数第 \(k\) 次邮票需要 \(k\) 元(反过来先加后加一样)
还是分情况:
-
下次不幸买到已有的 \(i\) 张,概率为 \(\frac i n\),本次花费 \(f[i]+1\) 元(注意本次以后还有 \(f[i]\) 次,\(+1\)),以后要 \(g[i]\) 元
-
幸运的买了没有的 \(n-i\) 张,概率为 \(\frac{n-i}n\),本次花费 \(f[i+1]+1\) 元(本次以后还有 \(f[i + 1]\) 次),以后要 \(g[i+1]\) 元
\(g[i] = \frac{i}{n} \times (g[i] + f[i] + 1) + \frac{n - i} n \times (g[i + 1] + f[i + 1] + 1)\)
两边都有 g[i],化简一下:
\(g[i] = \frac{i}{n - i} \times f[i] + f[i + 1] + g[i + 1] + \frac n {n - i}\)
易知 \(g[n] = f[n] = 0\),刚开始没邮票,求到 \(g[0]\) 即可
代码:
#include<bits/stdc++.h>
using namespace std;
int n;
double f[10010], g[10010];
int main()
{
scanf("%d", &n);
for(int i = n - 1; i >= 0; i--) f[i] = f[i + 1] + n * 1.0 / (n - i);
for(int i = n - 1; i >= 0; i--)
g[i] = i * 1.0 / (n - i) * f[i] + f[i + 1] + g[i + 1] + n * 1.0 / (n - i);
printf("%.2f", g[0]);
return 0;
}
又一道期望神题
这里只用求次数,状态很神奇:
\(f[i]\) 为从第 \(i\) 个灯要按到有 \(i-1\) 个灯要按的期望次数
推转移方程:
解释第二种情况:现在有 \(\frac i n\) 的概率按到要按的,一次就按到 \(i - 1\),还有 \(\frac{n - i} n\) 的概率按到不用按的,先从 \(i + 1\) 个按到第 \(i\) 个,再从第 \(i\) 个按到 \(i - 1\) 个(注意一开始按了一个,加 \(1\))
第一个很好求,最后加 \(k\) 即可
化简第2个:
\(\frac i n \times f[i] = \frac i n + \frac{n - i} n \times f[i + 1] + \frac{n - i} n\)
\(f[i] = \frac n i \times (\frac{n - i} n \times f[i + 1] + 1) = \frac{(n - i)\times f[i + 1] + n}i\)
注意一下状态结尾不是 \(f[n]\),因为我们总共不一定要 \(n\) 次
\(O(n\sqrt n)\) 求出按几次就好了(注意特判可能总共只要 \(k\) 次及以下)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, k, a[100010], fact[100010], inv[100010], mod = 100003, f[100010], ans, sum;
int main()
{
scanf("%lld%lld", &n, &k);
fact[0] = inv[1] = 1;
for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
for(int i = n; i > 0; i--)
if(a[i])
{
for(int j = 1; j * j <= i; j++)
if(i % j == 0)
{
a[j] ^= 1;
if(j * j != i) a[i / j] ^= 1;
}
sum++;
}
for(int i = 2; i < mod; i++) inv[i] = inv[mod % i] * (mod - mod / i) % mod; // 推逆元公式
for(int i = n; i > 0; i--) f[i] = (((n - i) * f[i + 1] % mod + n) % mod * inv[i]) % mod;
if(sum <= k) ans = sum;
else
{
for(int i = sum; i > k; i--) ans = (ans + f[i]) % mod;
ans = (ans + k) % mod;
}
for(int i = 1; i <= n; i++) ans = ans * i % mod;
printf("%lld", ans);
return 0;
}
设 \(f(i,j)\) 表示聪聪在 \(i\),可可在 \(j\) 时,聪聪抓到可可的期望步数
先预处理出图中点两两之间的距离
如果 \(i=j\),显然 \(f(i,j)=0\)
让聪聪先靠近可可 \(2\) 步,如果抓到了,\(f(i,j)=1\)
否则 \(f(i,j)=\sum_{k=j||(j,k)\in E} \frac 1 {deg_j+1} f(pos,k)\)
看起来要高斯消元,但复杂度不允许
仔细分析一下,由于聪聪一定会靠近可可,所以可可在 \(k\),聪聪在 \(pos\) 时,聪聪不可能回到 \(i\),因为这样就远离了可可
发现转移之间无环,可以直接记忆化搜索,或是建出图拓扑排序
int id(int x, int y) {return (x - 1) * n + y;}
void bfs(int st)
{
memset(dis[st], 0x3f, sizeof(dis[st]));
memset(vis, 0, sizeof(vis));
queue<int> q;
dis[st][st] = 0, q.push(st), vis[st] = 1;
while(!q.empty())
{
int t = q.front(); q.pop();
for(int i : edge[t])
if(!vis[i] && dis[st][i] > dis[st][t] + 1) vis[i] = 1, dis[st][i] = dis[st][t] + 1, q.push(i);
}
}
int step(int x, int y) // x 向 y 走 2 步
{
int mn = inf, lsh = inf;
for(int i : edge[x])
if(dis[i][y] == dis[x][y] - 1 && i < mn) mn = i;
lsh = mn, mn = inf;
for(int j : edge[lsh])
if(dis[j][y] == dis[lsh][y] - 1 && j < mn) mn = j;
return mn;
}
void topsort()
{
queue<int> q;
for(int i = 1; i <= n * n; ++i)
if(!deg[i]) q.push(i);
while(!q.empty())
{
int t = q.front(); q.pop();
lin[++cnt] = t;
for(int i : tu[t])
{
--deg[i];
if(deg[i] == 0) q.push(i);
}
}
}
int main()
{
n = read(), m = read(), a = read(), b = read();
for(int i = 1; i <= m; ++i)
{
u = read(), v = read();
edge[u].pb(v), edge[v].pb(u);
}
for(int i = 1; i <= n; ++i) bfs(i);
for(int i = 1; i <= n; ++i) // 聪聪在 i 可可在 j
for(int j = 1; j <= n; ++j)
{
if(i == j) f[i][j] = 0;
else if(dis[i][j] <= 2) f[i][j] = 1;
else
{
int pos = step(i, j); f[i][j] = -1;
for(int k : edge[j])
tu[id(pos, k)].pb(id(i, j)), ++deg[id(i, j)];
tu[id(pos, j)].pb(id(i, j)), ++deg[id(i, j)];
}
}
topsort();
for(int i = 1; i <= cnt; ++i)
{
int x = (lin[i] - 1) / n + 1, y = (lin[i] - 1) % n + 1;
if(f[x][y] >= 0) continue;
f[x][y] = 0;
int pos = step(x, y);
for(int j : edge[y])
f[x][y] += (f[pos][j] + 1) / (double)(edge[y].size() + 1);
f[x][y] += (f[pos][y] + 1) / (double)(edge[y].size() + 1);
}
printf("%.3lf", f[a][b]);
return 0;
}
结合其它 DP 方法
\(n\) 的范围显然得考虑状压 DP,设 \(f(i,s)\) 为已经抛了 \(i\) 个礼物,获得礼物的集合为 \(s\) 时剩下得分的最大期望
枚举这一轮的礼物种类,如果 \(s\) 中包含了所有的前置礼物,则决策可以时选它,\(f(i+1,s|2^j)+w_j\to f(i,s)\)
否则不能选,\(f(i+1,s)\to f(i,s)\)
稍微进行剪枝,预处理出合法的集合 \(s\)
int main()
{
k = read(), n = read();
for(int i = 1; i <= n; ++i)
{
p[i] = read(), u = read();
while(u) s[i] |= (1 << (u - 1)), u = read();
}
for(int i = 0; i < (1 << n); ++i)
{
int flag = 1;
for(int j = 1; j <= n; ++j)
if(((i >> (j - 1)) & 1) && (s[j] & i) != s[j]) {flag = 0; break;}
if(flag) ok.pb(i);
}
for(int i = k - 1; i >= 0; --i)
for(int v : ok)
{
for(int j = 1; j <= n; ++j)
{
double nw = -1e9;
if((s[j] & v) == s[j]) nw = max(nw, f[i + 1][v | (1 << (j - 1))] + (double)p[j] * 1.0);
nw = max(nw, f[i + 1][v]);
f[i][v] += nw / (double)n;
}
}
ans = f[0][0];
printf("%.6lf", ans);
return 0;
}
期望线性
期望的线性性很重要!
\(E(aX+bY)=E(aX)+E(bY)=aE(X)+bE(Y)\)
但是乘积的期望不那么好处理,尤其是相关的时候
通常是大力拆括号/组合意义/结合各种优化来 DP
还有将问题等价去掉限制,有的时候要大胆猜测
CF1842G Tenzing and Random Operations
计算 \(\prod_{i=1}^n (a_i+k_iv)\) 的期望
拆开的话很复杂,\(k_i\) 相乘的期望也不好算
但我们知道展开后一定是若干个 \(n\) 个数乘积的单项式相加
于是算出所有可能的选择方案中这些单项式的总和即可知道期望
考虑有这样的数表:
\(x_{i,j}\) 表示第 \(i\) 次操作,第 \(j\) 个位置是否被增加 \(v\)
每个位置的值为 \(0/1\),且一行中为 \(1\) 的一定是一个后缀(因为是后缀加)
操作次数很多,但是这个单项式只有 \(n\) 个数
每个位置的 \(+v\) 有能不能用,有没有用过两种状态
我们不关心它能不能用,我们只关心它用了没有,而且总共只会用 \(O(n)\) 个
也就是说,没有用到的操作我们不关心它具体的起点,将它们归为一类计算贡献
这样设计 \(f(i,j)\) 表示已经统计了前 \(i\) 个数,用了 \(j\) 次操作的总和
- 第 \(i\) 个数放 \(a_i\),\(f(i,j)\gets f(i-1,j)\times a_i\)
- 第 \(i\) 个数想选之前已用过的操作,此时根据 \(x\) 的性质,\(x\) 肯定是 \(1\),能用上,有 \(j\) 种选法,\(f(i,j)\gets f(i-1,j)\times j\times v\)
- 第 \(i\) 个数想选之前没用过的操作,必须把这个操作在前面某次就钦定为 \(1\),有 \(m-j+1\) 个操作,前面有 \(i\) 个可用起点可供选择,\(f(i,j)\gets f(i-1,j-1)\times(m-j+1)\times i\times v\)
最后统计答案,用了 \(i\) 次操作时,剩下 \(m-i\) 次操作可以随便指定起点,共 \(n^{m-i}\) 种方法
期望还要除以 \(n^m\),化简后 \(ans=\sum_{i=0}^{\min\{n,m\}} \dfrac {f(n,i)}{n^i}\)
或者直接 DP 期望,\(E(i,j)\) 为已经统计了前 \(i\) 个数,用了 \(j\) 次操作的某种摆放方案中单项式的总和的期望,放 \(a_i\) 期望直接乘 \(a_i\),放之前用过的,由于 \(x=1\) 所以一定能成功,期望 \(\times j\times v\),放没用过的,它有 \(\frac i n\) 的概率能成功,否则 \(x=0\),整个式子值直接为 \(0\),所以 \(E(i,j)\gets E(i-1,j-1)\times\frac i n\times (m-j+1)\times v\)
int main()
{
read(n, m, v);
for(ll i = 1; i <= n; ++i) read(a[i]);
f[0][0] = 1;
for(ll i = 1; i <= n; ++i)
{
for(ll j = 0; j <= i && j <= m; ++j)
{
f[i][j] = f[i - 1][j] * ((j * v % mod + a[i]) % mod) % mod;
if(j) f[i][j] = (f[i][j] + f[i - 1][j - 1] * (m - j + 1) % mod * i % mod * v % mod) % mod;
}
}
for(ll i = 0; i <= n && i <= m; ++i) ans = (ans + f[n][i] * qmi(qmi(n, i), mod - 2) % mod) % mod;
printf("%lld", ans);
return 0;
}
首先转化 \(E(\text{使所有点被选中的期望次数})=\sum_{i=1}^n E(i\ \text{被选中的次数})\),因为只有所有点被选中后才没有点被选,而期望有线性性,可以拆开
这样计算一个点被选中的次数,只跟这个点到根的这条链上的点有关
点 \(x\) 只有当链上所有点被选过后才不会被选,\(x\) 的深度为 \(d_x\)(根深度为 \(1\))
但是到根路径上全部染黑后这个点不能再选的限制不好处理
假设我们依然能选这些点,但不计算贡献,将问题变得对称,这是常见的处理手段
考虑随便选择时的操作序列,选这些点并不影响 \(x\) 的期望选择次数,而且也不会影响所有点的颜色,不选这些点就是把这些不合法操作从操作序列中去掉,但没有造成任何影响
因此,原问题与新问题等价
这样就好办了,设当前已选 \(k\) 个点,有 \(\frac k {d_x}\) 的概率不会选新的,则期望 \(\frac {d_x}{d_x-k}\) 步选到下一个点
总共期望 \(\sum_{k=0}^{d_x-1} \frac{d_x}{d_x-k}=d_x\sum_{i=1}^{d_x}\frac{1}{i}\) 步选完这条链
问题转化后,每个点被选的机会均等,所以 \(x\) 期望被选 \(\sum_{i=1}^{d_x}\frac 1 i\) 次
答案为 \(\sum_{i=1}^n H_{d_i}\)
int main()
{
read(n), dep[1] = 1;
for(int i = 2; i <= n; ++i) read(fa[i]), dep[i] = dep[fa[i]] + 1;
for(int i = 1; i <= n; ++i) h[i] = (h[i - 1] + qmi(i, mod - 2)) % mod;
for(int i = 1; i <= n; ++i) ans = (ans + h[dep[i]]) % mod;
printf("%lld", ans);
return 0;
}
做了这道题,就不再想说 「组合意义天地灭,数学推导保平安」
推式子我至今自己推不出来,CF 官方题解中的也非常复杂
但是可以用组合意义巧妙的解释这题
由于每轮均匀随机,期望抽到的张数相同,因此
根据期望的公式,\(\sum_{i=1}^{\infty} P(\text{在第 i-1 轮未结束})=E(\text{轮数})\)
因此,
先算一轮中抽的张数,可以大力枚举张数计算概率,但期望有线性性,可以单独拿出一张数字牌考虑它期望被抽的次数,它在所有鬼牌前才能被抽,概率为 \(\frac 1 {m+1}\),那么共 \(n\) 张牌,最后还要抽一张鬼牌结束,期望张数为 \(\frac n{m+1}+1\)
算轮数比较麻烦,设 \(f_i\) 为当前还剩 \(i\) 张数字牌没拿到时的剩余期望轮数
由于拿到已经拿到的牌不会结束一轮也不会有贡献,可以直接忽略
所以每轮开头可以看作是新牌或鬼牌,把抽到一张新牌的所有轮记作一回合
但是抽到新牌后,一轮不会结束,有可能再抽到新牌,所以一回合中先去掉抽出新牌的那一轮,最后再加上令游戏结束的最后一轮
这样下一回合的开头紧接着上一回合的结尾,每轮有 \(\frac {m}{m+i}\) 的概率开头为鬼牌,因此期望 \(\frac {m+i}i\) 轮进入下一回合
去掉最后一轮后,\(f_i=\frac m i+f_{i-1}\)
答案即为 \((mH_n+1)(\dfrac n{m+1}+1)\)
int main()
{
cin >> n >> m;
fact[0] = invf[0] = 1, V = n + m;
for(ll i = 1; i <= V; ++i) fact[i] = fact[i - 1] * i % mod;
invf[V] = qmi(fact[V], mod - 2);
for(ll i = V - 1; i > 0; --i) invf[i] = invf[i + 1] * (i + 1) % mod;
for(ll i = 1; i <= n; ++i) h[i] = (h[i - 1] + invf[i] * fact[i - 1] % mod) % mod;
eturn = (m * h[n] % mod + 1) % mod;
for(ll i = 1; i <= n + 1; ++i) estep = (estep + invf[n - i + 1] % mod * fact[n + m - i] % mod * i % mod) % mod;
estep = estep * fact[n] % mod * invf[m + n] % mod * m % mod;
printf("%lld", estep * eturn % mod);
return 0;
}
高次期望
\(solution\):
拆括号,\((x+1)^3=x^3+3x^2+3x+1\)
考虑一下当新加入位置 \(i\) 时,相对 \(i-1\) 会产生 \(3x_{i-1}^2+3x_{i-1}+1\) 的新增贡献,计算它即可
注意这里新增是相对上个位置填完后强制计算后缀的贡献后而言
有 \(1-p_i\) 的概率填 0,结束这一段,贡献不新增
有 \(p_i\) 概率填 1,此时
-
对于 \(x\) 产生 \(x_1[i]=(x_1[i-1]+1)\times p[i]\) 新增贡献
-
对于 \(x^2\) 产生 \(x_2[i]=(x_2[i-1]+2x_1[i-1]+1)\times p[i]\) 新增贡献
-
对于 \(x^3\) 即答案,\(f[i]\) 为到第 \(i\) 位的总贡献,产生 \(x_3[i]=(3x_2[i-1]+3x_1[i-1]+1)\times p[i]\) 新增贡献,但统计的是总贡献,所以还要加上到前一位的总贡献 \(f[i-1]\)
所以 \(f[i]=f[i-1]+(3x_2[i-1]+3x_1[i-1]+1)\times p[i]\)
答案为 \(f[n]\)
小问题:为什么 \(x_3[i]\) 的新增贡献中没有像 \(x_1[i],x_2[i]\) 那样加上前一项呢?
其实比较简单,因为对于最终答案来说,新增的是整个 \(x\) 和 \(x^2\),式子后面加的是对于上一位时新增的 \(x\)、\(x^2\) 来说这一位\(x\)、\(x^2\) 新增的,但对上个 \(x^3\) 而言只新增了 \(3x^2+3x+1\),前一项 \(x^3\) 是到上一位的总和
即相当于拆开了每一位的贡献,把每一位的贡献累加起来
(有点绕)
\(code\):
for(reg int i = 1; i <= n; ++i)
{
x1[i] = (x1[i - 1] + 1) * p[i];
x2[i] = (x2[i - 1] + 2 * x1[i - 1] + 1) * p[i];
f[i] = f[i - 1] + (3 * x1[i - 1] + 3 * x2[i - 1] + 1) * p[i];
}
概率
常见公式
一些太基础的就不写了
条件概率公式:\(P(AB)=P(A|B)P(B)=P(B|A)P(A)\)
这里的 \(P(A|B)\) 指事件 \(A\) 在 \(B\) 已经发生时发生的概率
全概率公式:如果一组事件 \(C_{1\sim n}\) 满足它们之间恰好有一个会发生,即 \(\sum_{i=1}^n P(C_i)=1\),那么
常用的特例:\(P(A)=P(A|B)P(B)+P(A|\overline B)P(\overline B)\)
贝叶斯公式:可以由条件概率公式推导而来,\(P(A|B)=\dfrac{P(AB)}{P(B)}=\dfrac{P(B|A)P(A)}{P(B)}\)
概率分布
用于表述随机变量取值的概率规律
常见分布:
- 两点分布,又称伯努利分布
\(X\) 只有 \(0,1\) 两种取值,有 \(p\) 的概率是 \(1\),\(1-p\) 的概率是 \(0\)
- 几何分布,设在伯努利实验中,\(X\) 为得到一次成功需要的实验次数,那么 \(P[X=k]=(1-p)^{k-1}p\)
它的期望 \(E[X]=\frac 1 p\),可以用等比数列求和推,也可以用 DP 推出
场上并不知道分布
但是发现每个位置的期望大小相同为 \(E\),且每个位置独立,答案是 \(\frac {r-l+1}E\)
然后由于精度要求不高,暴力算了数列前 \(5\times 10^5\) 项就算出了 \(E\),过了
现在知道分布后,\(E=\frac 1 p\),答案就是 \((r-l+1)p\)
官方题解很妙:看作一排灯,每盏灯有 \(p\) 的概率亮起,序列中每个位置的大小就是第一盏亮着的灯的位置,然后把它后面的那盏灯记为第一盏,以此类推,得到后面位置的大小,那么问题转化为 \([l,r]\) 内期望有多少盏亮着的灯,答案为 \((r-l+1)p\)
- 二项分布,描述进行 \(n\) 次伯努利实验成功的次数,\(P[X=k]={n\choose k}p^k(1-p)^{n-k}\)
期望为 \(E[X]=np\),还是比较好理解的,一次实验成功次数的期望是 \(p\),期望有线性性
- 超几何分布,描述抽样不放回,\(N\) 个产品中 \(K\) 个不合格,现在抽 \(n\) 个送检,设 \(X\) 为不合格样品数量
则 \(P[X=k]=\dfrac{{K\choose k}{N-K\choose n-k}}{N\choose n}\),总方案数除以选择方案数,期望 \(E[X]=n\frac K N\),证明比较复杂,注意虽然前一次抽取会影响样本空间,但概率权重会把影响抵消
树上处理双向贡献
期望没什么用,实际求的是每个节点有电的概率之和
发现某个节点通电,要么它自己有电,要么它的父节点或子节点有电传给它
如果设 \(f_i\) 表示 \(i\) 有电的概率,那么发现它要从父节点,子节点同时转移过来,没法做
考虑先只处理子节点,注意子节点 \(i\) 有电与 \(j\) 有电并不互斥,所以要用容斥原理算出 \(i\) 或 \(j\) 通电的概率
这样根节点处的概率已经算对了
然后处理父节点,此时父节点通电的概率就要去掉从当前子树通电的情况
同样用容斥,设 \(P(A)\) 为 父节点不从当前节点通电的概率,\(P(B)\) 为当前节点到父节点有电的概率,\(P(B)=f_y\times p_{x,y}\)
\(P(A)+P(B)-P(A)P(B)=f_x\),解方程得 \(P(A)=\frac {f_x-P(B)}{1-P(B)}\)
则 \(f_y=f_y+P(A)\times p_{x,y}-f_y\times P(A)\times p_{x,y}\),仍然是容斥原理计算
细节:如果 \(P(B)=1\),则可以直接跳过,因为始终有电了,防止除以 \(0\)
#include<bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int N = 500010, SZ = (1 << 14) + 5; const double eps = 1e-9;
typedef pair<int, int> pii;
int n, u, v, w, p[N], fa[N];
vector<pii> edge[N];
double f[N], ans;
static char buf[SZ], *bgn = buf, *til = buf;
char getc()
{
if(bgn == til) bgn = buf, til = buf + fread(buf, 1, SZ, stdin);
return bgn == til ? EOF : *bgn++;
}
int read()
{
char ch = getc(); int x = 0;
while(ch < '0' || ch > '9') ch = getc();
while(ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getc();
return x;
}
void dfs1(int x, int fath) // 算只从子节点处通电的概率
{
fa[x] = fath, f[x] = (double)p[x] * 1.0 / 100.0;
for(pii y : edge[x])
if(y.fi != fath)
{
dfs1(y.fi, x);
double pr = f[y.fi] * y.se * 1.0 / 100.0;
f[x] += pr - f[x] * pr; // 容斥,因为两个事件不互斥,都发生的概率为 P(A)+P(B)-P(AB)
}
}
void dfs2(int x) // 加上从父节点处通电的概率(初始根节点就是对的)
{
for(pii y : edge[x])
{
if(y.fi == fa[x]) continue;
double pr = f[y.fi] * y.se / 100.0;
if(fabs(pr - 1.0) > eps)
{
double pa = (f[x] - pr) / (1.0 - pr); // x不从 y处通电的概率
f[y.fi] += pa * y.se / 100.0 - f[y.fi] * pa * y.se / 100.0;
}
dfs2(y.fi);
}
}
int main()
{
n = read();
for(int i = 1; i < n; ++i)
{
u = read(), v = read(), w = read();
edge[u].pb(mp(v, w)), edge[v].pb(mp(u, w));
}
for(int i = 1; i <= n; ++i) p[i] = read();
dfs1(1, 0), dfs2(1);
for(int i = 1; i <= n; ++i) ans += f[i];
printf("%.6lf", ans);
return 0;
}
轮数处理
把期望拆成每张卡牌发动的概率 \(\times\) 分数,实际上是求概率
一轮一轮直接求肯定不好求,考虑从整体计算
如果有 \(r\) 轮决策到了卡牌 \(x\),则都不发动的概率为 \((1-p_x)^r\),发动的概率为 \(1-(1-p_x)^r\)
但是如果一轮中前面的卡牌有发动的,这一轮就轮不到它
设 \(f(i,j)\) 为前 \(i\) 张卡牌,发动了 \(j\) 张的概率
则第 \(i\) 张有 \(r-j\) 轮轮到了它决策,分它发动和不发动两种情况转移
设 \(s_i\) 为第 \(i\) 张卡牌发动的总概率,则 \(s_i=\sum_{j=0}^i f(i-1,j)\times (1-(1-p_i)^{r-j})\)
int main()
{
ios::sync_with_stdio(false), cin.tie(0);
cin >> t;
while(t--)
{
memset(f, 0, sizeof(f)), memset(pw, 0, sizeof(pw));
cin >> n >> r, ans = 0;
for(int i = 1; i <= n; ++i) cin >> p[i] >> a[i], s[i] = 0, pw[i][0] = 1;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= r; ++j) pw[i][j] = pw[i][j - 1] * (1 - p[i]); // 第 i张卡在 j轮中不发动的概率
f[0][0] = 1; // f[i][j]:前 i张,有 j张发动了技能的概率
for(int i = 1; i <= n; ++i)
{
for(int j = 0; j <= i && j <= r; ++j) // 如果某一轮前面有发动的卡,那么这一轮它一定不会发动
{
if(j) f[i][j] = f[i - 1][j] * pw[i][r - j] + f[i - 1][j - 1] * (1 - pw[i][r - j + 1]);
else f[i][j] = f[i - 1][j] * pw[i][r - j];
s[i] += f[i - 1][j] * (1 - pw[i][r - j]);
}
}
for(int i = 1; i <= n; ++i) ans += a[i] * s[i];
printf("%.10Lf\n", ans);
}
return 0;
}
杂项
很 NB 的概率题
一看可以覆盖实数,显然不好做
但线段长度均为整数,如果我们知道了它们小数部分的相对大小,则只用知道覆盖到哪个整数就能知道是否覆盖
\(n\) 很小,我们完全可以 \(O(n!)\) 枚举相对大小,排序后 DP
注意这里断环为链必须选取最长的放在起点,避免某条线段在最后时转了一圈完全覆盖起点
那么每个整数相当于拆成了 \(n\) 个数,第 \(i\) 个必须放在对应小数部分相对大小对应的点
设 \(f_{s,j}\) 为集合 \(s\) 中的线段,覆盖了 \([0,j]\) 的方案数
转移则先从小到大枚举下一条线段放置的位置,强制从前往后放,避免顺序问题,然后枚举覆盖到的位置和已经放置的集合,状压 DP
最后放置的总方案数为 \((n-1)!\times c^{n-1}\),因为除了第一个,共有 \((n-1)!\) 种大小顺序,其它线段都有 \(c\) 种放置方法
int main()
{s
cin >> n >> c;
for(int i = 1; i <= n; ++i) cin >> a[i], id[i] = i;
sort(a + 1, a + n + 1, [](const int x, const int y){return x > y;});
do
{
memset(f, 0, sizeof(f)); // f[i][s]: cover [0,i], use arcs in state s
f[a[id[1]] * n][1] = 1, ++cnt; // the longest arc must be in the beginning
for(int i = 1; i < n * c; ++i) // place one in i
{
int nw = i % n + 1;
if(nw == 1) continue;
for(int j = i; j <= n * c; ++j)
for(int k = 1; k < (1 << n); k += 2)
if(!(k >> (nw - 1) & 1))
f[min(n * c, max(j, i + a[id[nw]] * n))][k | (1 << (nw - 1))] += f[j][k];
}
ans += f[n * c][(1 << n) - 1];
}while(next_permutation(id + 2, id + n + 1));
printf("%.16Lf", (long double)ans / (long double)cnt / (long double)pow((long double)c, n - 1));
return 0;
}