概率和期望

概率和期望

期望

常见技巧与知识

  • 如果当前步数通往下一步时,有 \(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\),那么

\[E(A)=\sum_{i=1}^n E(A|C_i)P(C_i) \]

这在 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种情况讨论:

  1. 下次不幸买到已有的i张,概率为 \(\frac i n\),还要再用 \(f[i]\) 的次数买

  2. 幸运的买了没有的 \(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\)(反过来先加后加一样)

还是分情况:

  1. 下次不幸买到已有的 \(i\) 张,概率为 \(\frac i n\),本次花费 \(f[i]+1\) 元(注意本次以后还有 \(f[i]\)\(+1\)),以后要 \(g[i]\)

  2. 幸运的买了没有的 \(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;
}

T2:P3750 [六省联考 2017] 分手是祝愿

又一道期望神题

这里只用求次数,状态很神奇:

\(f[i]\) 为从\(i\) 个灯要按到有 \(i-1\) 个灯要按的期望次数

推转移方程:

\[f[i] = \begin{cases} i,&i <= k\\ \frac i n + \frac{n - i} n \times (f[i + 1] + f[i] + 1),& i > k \end{cases} \]

解释第二种情况:现在有 \(\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;
}

T3:P4206 [NOI2005] 聪聪与可可

\(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 方法

T4:P2473 [SCOI2008] 奖励关

\(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\) 个数乘积的单项式相加

于是算出所有可能的选择方案中这些单项式的总和即可知道期望

考虑有这样的数表:

\[\begin{bmatrix} x_{1,1} & x_{1,2} &\dots&x_{1,n-1}&x_{1,n}\\ x_{2,1} & x_{2,2} &\dots&x_{2,n-1} &x_{2,n}\\ &&\dots\\ x_{m,1} & x_{m,2} &\dots&x_{m,n-1} &x_{m,n} \end{bmatrix} \]

\(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;
}

ARC150D Removing Gacha

首先转化 \(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;
}

CF1392H ZS Shuffles Cards

做了这道题,就不再想说 「组合意义天地灭,数学推导保平安」

推式子我至今自己推不出来,CF 官方题解中的也非常复杂

但是可以用组合意义巧妙的解释这题

\[E(\text{抽到所有牌的次数})=\sum_{i=1}^{\infty} P(\text{在第 i-1 轮未结束})\times E(\text{第 i 轮抽的张数}) \]

由于每轮均匀随机,期望抽到的张数相同,因此

\[=\sum_{i=1}^{\infty} P(\text{在第 i-1 轮未结束})\times E(\text{一轮中抽的张数}) =E(\text{一轮中抽的张数})\times \sum_{i=1}^{\infty} P(\text{在第 i-1 轮未结束}) \]

根据期望的公式,\(\sum_{i=1}^{\infty} P(\text{在第 i-1 轮未结束})=E(\text{轮数})\)

因此,

\[E(\text{抽到所有牌的次数})=E(\text{轮数})\times 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}\)

\[E(\text{轮数})=\sum_{i=1}^n f_i+1=m\sum_{i=1}^n\frac 1 i+1=mH_n+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;
}

高次期望

P1654 OSU!

\(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)=\sum_{i=1}^n P(AC_i)=\sum_{i=1}^n P(A|C_i)P(C_i) \]

常用的特例:\(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 推出

P9963 [THUPC 2024 初赛] 前缀和

场上并不知道分布

但是发现每个位置的期望大小相同为 \(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\),证明比较复杂,注意虽然前一次抽取会影响样本空间,但概率权重会把影响抵消

树上处理双向贡献

P4284 [SHOI2014] 概率充电器

期望没什么用,实际求的是每个节点有电的概率之和

发现某个节点通电,要么它自己有电,要么它的父节点或子节点有电传给它

如果设 \(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;
}

轮数处理

P3239 [HNOI2015] 亚瑟王

把期望拆成每张卡牌发动的概率 \(\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;
}

杂项

[AGC020F] Arcs on a Circle

很 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;
}
posted @ 2024-02-14 22:26  KellyWLJ  阅读(19)  评论(0编辑  收藏  举报