数学期望 DP

https://notes.sshwy.name/Math/Expectation/Classic

https://ac.nowcoder.com/acm/contest/32282

对于一组离散型随机变量,出现其中某一变量的概率乘以这一变量值,再求和,就是数学期望。

也就是:

\(E(X)=∑\limits_{i}(p(X=i)×i)\)
通过这个定义,我们可以感知到,所谓期望,其实表示的是一组离散型随机变量的平均水平。 也可认为是进行某件事能得到的平均结果,或者理想代价。所以它也可以叫做一组离散型随机变量的均值。这也是期望这个概念的实际意义。

关于概率的一些性质:

  • 如果事件 \(A\)\(B\) 为互斥事件(不会既满足 \(A\) 又满足 \(B\))那么 \(p(A||B)=p(A)+p(B)\)
  • 如果事件 \(A\)\(B\) 为独立事件(两个事件不相关)那么 \(p(A\&\&B)=p(A) \times p(B)\)

关于期望的一些性质:

  • \(E(X+Y)=E(X)+E(Y)\)
    证明:\(E(X+Y)=\sum\limits_i (\sum\limits_j^i p(X=j\&\&Y=i-j) \times i)=\sum\limits_j p(X=j) \times j + \sum\limits_k^{(k=i-j)} p(Y=k) \times k = E(X) + E(Y)\)

  • \(E(XY)=E(X)E(Y)\),X,Y 相互独立
    证明:
    \(E(XY)=\sum\limits_i\sum\limits_j p(i=X\&\&j=Y) \times i \times j=\sum\limits_i\sum\limits_j p(i=X)i \times p(j=Y)j=E(X)E(Y)\)

  • \(E(aX+b)=aE(X)+b\)

  • \(E(c)=c\)

  • 前缀和技巧:对于离散变量(取值只有整数)\(x\)\(P(x=k)=P(x \le k) - P(x \le k-1)\)

其中,当\(E(XY)=E(X)E(Y)\)的成立条件是 X,Y 相互独立(两个事件互不影响),这个式子反过来也是 X,Y 相互独立的判定条件。

以上是高中数学内容。在此重新提一下。

进行期望DP的时候,这些性质有时显得至关重要,可以帮助我们理解很多递推的转移。

ABC263E

题意:

\(n\) 个格子,在第 \(1 \sim n-1\) 个格子上,第 \(i\) 个格子上有一个骰子,面值为 \(0 \sim a_i\),投一次骰子,这些面值均匀出现。投到几,就往前走几格。

\(1\) 号格子出发,求到 \(n\) 号格子所要投的骰子个数的数学期望。

分析:

对于线性期望 DP,通常有两种 DP 方式:

  1. \(dp_i\) 表示从起始状态到 \(i\) 状态的数学期望。
  2. \(dp_i\) 表示从 \(i\) 状态到起始状态的数学期望。

转移的时候,第一种方式通常从前往后转,需要起始状态已知。第二种方式通常是从后往前转,需要终止状态已知。有的时候这两种方式可以互换,有的时候不可以,需要灵活使用。

本题先考虑使用第 \(2\) 种方式,因为我们知道一个点可以前往哪些点,比较方便。
\(dp_i\) 表示从 \(i\) 节点开始,到达 \(n\) 的数学期望。

先从有穷条件入手,即如果骰子可以投到 \(1 \sim a_i\)(这样不会出现在原地打转的情况,比较符合拓扑序,方便入手),那么应该怎么算。
显然,

\[dp_i = \cfrac{dp_{i+1}+dp_{i+2}+...+dp_{i+a_i}}{a_i} + 1 \]

(投到 \(1,2,...,a_i\) 每一种情况的概率是 \(\frac{1}{a_i}\);不论什么情况从 \(i\) 都要多投 \(1\) 粒骰子)

如果是原题的无穷条件呢?在这时,虽然有些情况需要投掷的骰子数量会趋于无穷,但这时的概率成指数型增长。数学期望依然收敛。怎么计算呢?
照算不误。(by ajh 大佬)
我们假装 \(dp_i\) 已经算好了。那么有以下式子,即为等量关系。所以可以划到同一边当作递推式。

\[dp_i = \cfrac{dp_i+dp_{i+1}+dp_{i+2}+...+dp_{i+a_i}}{a_i+1}+1 \]

\(dp_i\) 提到左边有:

\[\cfrac{a_i}{a_i+1} dp_i=\cfrac{dp_{i+1}+dp_{i+2}+...+dp_{i+a_i}}{a_i+1} + 1 \]

那么,

\[dp_i=\cfrac{a_i+1}{a_i} (\cfrac{dp_{i+1}+dp_{i+2}+...+dp_{i+a_i}}{a_i+1} + 1) \\ =\cfrac{dp_{i+1}+dp_{i+2}+...+dp_{i+a_i}+a_i+1}{a_i} \]

时间复杂度 \(O(n \log \bmod)\),使用后缀和优化。

从另一种角度思考。考虑这个人投了平均 \(X\) 次骰子都是 \(0\) 之后才投到不是 \(0\) 的。
那么有: \(dp_i=\cfrac{\sum \limits_{j=1}^{a_i} dp_j} {a_i} + X + 1\),其中 \(X\) 为在 \(i\) 处投到 \(0\) 的期望次数。(继承上面有穷条件的思路)
考虑求 \(X\)。画个树状图看看。
image
发现刚好\(0\) 次的概率为 \(\cfrac{a_i}{a_i+1}\)(前缀和技巧,至少 \(0\) 次 - 至少 \(1\) 次),刚好为 \(1\) 次的概率为 \(\cfrac{a_i}{(a_i+1)^2}\),...,刚好为 \(j\) 次的概率为 \(\cfrac{a_i}{(a_i+1)^{j+1}}\)
那么 \(X = \sum \limits_{j=0}^{∞} j \times \cfrac{a_i}{(a_i+1)^{j+1}}\)
这个东西怎么求呢?它是无穷幂级数。这东西有判敛法,改天学学。现在只要知道怎么计算即可。
错位相减法。等比数列求和中用过。这题怎么用?令 \(X = a_i \times S,x = (a_i+1)\),则有:

\[S = (\cfrac{0}{x^1}+\cfrac{1}{x^2}+\cfrac{2}{x^3}+\cfrac{3}{x^4}+...) \]

\[xS = (\cfrac{0}{x^0}+\cfrac{1}{x^1}+\cfrac{2}{x^2}+\cfrac{3}{x^3}+...) \]

\[xS-S = (\cfrac{1}{x^1}+\cfrac{1}{x^2}+\cfrac{1}{x^3}+\cfrac{1}{x^4}+...) \]

右边是等比数列,用前 \(n\) 项和公式计算:

\[(x-1)S = \cfrac{\cfrac{1}{x} \times [1-(\cfrac{1}{x}^∞)]}{1-\cfrac{1}{x}} \]

注意到 \(\cfrac{1}{x}^∞ = 0\),化简得

\[S = \cfrac{1}{(x-1)^2} \]

也就是

\[X = \cfrac{1}{a_i^2} \times a_i = \cfrac{1}{a_i} \]

得到的式子和之前是一样的。
甚至我们可以直接猜出来在一个位置上期望投出 \(\cfrac{1}{a_i}\) 个骰子,这是有一个定理的:
【定理】
\(\mathbf{概率为~p~的事件期望在第~\cfrac{1}{p}~次发生。}\)
证明和上面类似,套公式即可。
使用这个定理,“投出不是 \(0\) 的” 的概率是 \(\cfrac{a_i}{a_i+1}\),那么期望第 \(\cfrac{a_i+1}{a_i}\) 次发生。同样可以!

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1000000;
const int mod = 998244353;
int a[200010];
int dp[200010];
int suf[200010];
int qpow(int x, int k){
    int ans = 1;
    while(k) {
        if(k&1)ans=ans*x%mod;
        x=x*x%mod;
        k>>=1;
    }
    return ans;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    //think twice,code once.
    //think once,debug forever.
    int n; cin >> n;
    f(i ,1,n)cin>>a[i];
    dp[n] = 0;
    for(int i = n-1;i >= 1; i--){
        int up = ((suf[i+1]-suf[i+a[i]+1])+a[i]+1+inf*mod) % mod;
        int down = a[i];
        dp[i] = up * qpow(down, mod - 2) % mod;
        suf[i] = (suf[i+1]+dp[i])%mod;
    }
    cout << dp[1] << endl;
    return 0;
}

拿球

  1. 箱子里有 \(n\) 个球 \(1,2,⋯,n\),你要从里面拿 \(m\) 次球,拿了后放回,求取出的数字之和的期望。
    一眼题,用到了 \(E(X+Y)=E(X)+E(Y)\) 的定律。
    \(E(\sum x_i)=\sum E(x_i)= \cfrac{(n+1)m}{2}\)

  2. 箱子里有 \(n\) 个球 \(1,2,⋯,n\),你要从里面拿 \(m\) 次球,拿了后不放回,求取出的数字之和的期望。
    换个思路,设 \(x_i\) 为:\(i,if~i~was~chosen \\ 0,otherwise\)
    那么 \(S=\sum \limits_{i=1}^n x_i\)
    那么 \(E(S) = E(\sum x_i) = \sum E(x_i)\)
    \(\sum E(x_i) = \sum \limits_j p(x_i=j) \times j = p(x_i=i)\times i\)
    这个 \(p(x_i=i)\) 就等于 \(\cfrac{m}{n}\),那么原式还是等于 \(\cfrac{(n+1)m}{2}\)
    设置随机变量是非常重要的!这个问题中设置的是对答案的贡献。

  3. 箱子里有 \(n\) 个球 \(1,2,⋯,n\),你要从里面拿 \(m\) 次球,拿了后以 \(p_1\) 的概率放回,\(p_2\) 的概率放回两个和这个相同的球(相当于增加一个球),求取出的数字之和的期望。
    \(x_i\) 为标号为 \(i\) 的球对答案的贡献,即被拿出来的次数乘以 \(i\)。设 \(y_i\) 为被拿出来的次数。
    那么 \(E(S) = \sum \limits_{i=1}^n E(x_i) = \sum \limits_{i=1}^n E(y_i) \times i\)
    我们需要求的是 \(E(y_i)\)。这里不仅用了期望的线性加公式,还神来之笔地用到了 \(E(c)=c\)
    由于 \(E(y_1)=E(y_2)=...=E(y_n)\) (地位均等)
    所以 \(E(y_1)+E(y_2)+...+E(y_n)=E(y_1+y_2+...+y_n)=E(m)=m\)
    \(E(y_1)=\cfrac{m}{n}\)
    那么代入原式得 \(E(S) = \cfrac{(n+1)m}{2}\)

总结:这三种游戏,不管是放回还是不放回还是放回两个,都是地位均等的,结论都是 \(E(S) = \cfrac{(n+1)m}{2}\)。另外我们需要巧妙寻找设置元素的方案,使得解题更快。

C

一个骰子有 \(m\) 面,第一个面有一个点,第二面有两个点,以此类推。Twilight Sparkle 确定投掷骰子时,每一面都是等概率出现的,即每面出现的概率为 \(\cfrac{1}{m}\)。并且她知道每次投掷的结果是独立的。帮助她计算投掷 \(n\) 次骰子所能获得最大值的期望。

分析:

\[E(\max\{x_i\})= \sum \limits_{j=1}^m p(\max\{x_i\}=j) \times j \]

前缀和套路,

\[p(\max\{x_i\}=j) = p(x_1 \le j \&\& x_2 \le j... \&\& x_3 \le j...x_n \le j)- p(x_1 < j \&\& x_2 < j... \&\& x_3 < j...x_n < j) \]

概率独立可加性,

\[p(\max\{x_i\}=j) = p(x_1 \le j) + p(x_2 \le j) + ... + p(x_n \le j) - p(x_1 < j) - p(x_2 < j) - ... - p(x_n < j) = (\cfrac{j}{m})^n - (\cfrac{j-1}{m})^n \]

代回原式,得:

\[E(\max\{x_i\}) = \sum \limits_{i=1}^m i((\cfrac{j}{m})^n - (\cfrac{j-1}{m})^n) \]

如果是可以取模,算到这里就行了,时间复杂度 \(O(m \log n)\)
不能取模的话把 sum 拆开来,发现是 \(m\) 减去一堆 \((\cfrac{i}{m})^n\),具体是:

\[m - \sum \limits_{i=1}^{m-1} (\cfrac{i}{m})^n \]

这东西先算 \(\cfrac{i}{m}\) 直接快速幂不会溢出。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long double ld;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
int m, n;
ld qpow(ld x, int k){
    ld ans=1;
    while(k){
        if(k&1)ans=ans*x;
        x=x*x;
        k>>=1;
    }
    return ans;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    //think twice,code once.
    //think once,debug forever.
    cin >> m >> n;
    ld ans= m;
    f(i, 1, m-1){
        ans -= qpow((i*1.0/m), n);
    }
    cout << fixed<<setprecision(10)<<ans << endl;
    return 0;
}

CF1754E
题意:有一个 01 序列,每次可以选择两个元素,如果为逆序则交换,否则不变,无论是否交换都算一次操作。问排好序的期望操作次数。

分析:
两个环节,第一个环节可能需要灵感,第二个环节是套路。

状态设计,容易想到使用 DP 计算,但状态并不是很好想。首先状态必须有单向性,必须有严格的 DP 顺序,于是我们可以想到用逆序对数来记录状态。然而,思考会发现并不可行。不仅是复杂度无法接受,仅用逆序对也无法完整地表示状态。实际上,我们可以用“未归位的数字个数”来作为状态。具体地,假设当前序列为 \(0110101\),该序列一共有三个 0,所以前三位应该都是 0,而实际上有两个 1,就定义此时的“未归位的数字个数”为 \(x=3\)。这个状态的单向性显然,完整性也容易证明,因为只有“归位”的交换是有意义的,如果没有达到“归位”的效果,无论是否交换都是没有意义的。

转移,首先考虑设计向前还是向后转。如果设计 \(dp_i\) 表示走到有 \(i\) 个未归位的期望次数,那么 \(dp_x\) 需要计算无穷级数不好转移。但是如果设计 \(dp_i\) 表示走到有 \(i\) 个未归位之后期望还要几步,那么 \(dp_0=0\) 就很好转移了。其次考虑转移方程式。最好用经典的“自己转到自己”的套路,并且牢记基本公式 \(E(X)=∑\limits_{i}(p(X=i)×i)\)。那么也就是:

\[dp_i = \cfrac{2i^2}{n(n-1)} \times dp_{i-1} + \cfrac{n(n-1) - 2i^2}{n(n-1)} \times dp_i + 1 \]

稍微化简即可得到

\[dp_i = dp_{i-1} + \cfrac{n(n-1)}{2i^2} \]

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
const int mod = 998244353;
void cmax(int &x, int y) {if(x < y) x = y;}
void cmin(int &x, int y) {if(x > y) x = y;}
int dp[3000010];
int qpow(int x, int k) {
	int ans  =1;
	while(k){
		if(k&1)ans=ans*x%mod;
		x=x*x%mod;
		k>>=1;
	}
	return ans;
}
int a[3000010];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    int T; cin >> T;
    while(T--) {
		int n; cin >> n;
		f(i, 0, n - 1) cin >> a[i];
		int cnt = 0;
		f(i, 0, n - 1) if(a[i] == 0) cnt++;
		int cw = 0;
		f(i, 0, cnt - 1) if(a[i] == 1) cw++;
		f(i, 1, cw) dp[i] = (dp[i - 1] + n * (n - 1) % mod * qpow(2 * i % mod * i % mod, mod - 2) % mod) % mod;
		cout << dp[cw] << endl; 
    }
    time_t finish = clock();
    //cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
}

P8774 爬树的甲壳虫

【题意】
有一只甲壳虫想要爬上一颗高度为 \(n\) 的树,它一开始位于树根, 高度为 \(0\),当它尝试从高度 \(i-1\) 爬到高度为 \(i\) 的位置时有 \(P_{i}\) 的概率会掉回树根, 求它从树根爬到树顶时, 经过的时间的期望值是多少。
\(n \le 10^5\)
【分析】
\(dp_i\) 表示还要爬多久。
有递推式 \(dp_i = (1 - p_i) \times dp_{i + 1} + p_i \times dp_0 + 1\)

推完递推式别傻愣着,看看怎么处理!

由于 \(dp_n = 0\),考虑递推。

\[dp_{i} \\ = (1 - p_i) \times dp_{i + 1} + p_i \times dp_0 + 1 \\ = (1-p_1) \times ((1-p_{i+1}dp_{i+2} + p_{i+1}dp_0 + 1) + p_i dp_0 + 1 \\ = ...\\ =X_i dp_n + Y_i dp_0 + C_i\\ = Y_i dp_0 + C_i \]

考虑递推到 \(dp_0\),就可以出式子了。
\(Y_i = (1 - p_{i}) \times Y_{i + 1} + p_{i}\)
\(C_i = (1 - p_{i}) \times C_{i + 1} + 1\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
void cmax(int &x, int y) {if(x < y) x = y;}
void cmin(int &x, int y) {if(x > y) x = y;}
int x[100100], y[100010];
const int mod = 998244353;
int p[100010], q[100010], c[100010];
int qpow(int x, int k) {
    int ans = 1;
    while(k){
        if(k&1) ans=ans*x%mod;
        x=x*x%mod;
        k>>=1;
    }
    return ans;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    int n; cin >> n;
    f(i, 0, n - 1) {
        cin >> x[i] >> y[i];
        p[i] = x[i] * qpow(y[i], mod - 2) % mod;
    }
    q[n-1]=p[n-1];
    c[n - 1] = 1;
    for(int i = n - 2; i >= 0; i--) {
        q[i] = ((1 - p[i] + mod) % mod) * q[i + 1] % mod + p[i];
        q[i] %= mod;
        c[i] = (1 - p[i] + mod) %mod*c[i+1]%mod + 1;
    }
    cout << c[0] * qpow((1 - q[0] + mod) % mod, mod - 2) % mod;
    time_t finish = clock();
    //cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
} 

线性递推?观察式子!寻找出路!

相互推导又怎样?完全可以一起求。

P3232

【题意】
给定一个 \(n\) 个点 \(m\) 条边的无向连通图,顶点从 \(1\) 编号到 \(n\),边从 \(1\) 编号到 \(m\)

小 Z 在该图上进行随机游走,初始时小 Z 在 \(1\) 号顶点,每一步小 Z 以相等的概率随机选择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小 Z 到达 \(n\) 号顶点时游走结束,总分为所有获得的分数之和。 现在,请你对这 \(m\) 条边进行编号,使得小 Z 获得的总分的期望值最小。
【分析】
首先,求出每条边期望经过的次数,然后从大到小赋权值即可。考虑 \(g_{u,v}\) 表示 \(u-v\) 这条边期望被走到的次数。考虑转化为点的期望次数这样才好做,那么有:

\[g_{u,v} = \cfrac{f_u}{e_u} + \cfrac{f_v}{e_v} \]

为什么呢?考虑走到 \(u\) 之后,有 \(\cfrac{1}{e_u}\) 的期望会走这条边。
其中 \(f_u\) 表示 \(u\) 的期望经过次数,\(e_u\) 表示 \(u\) 的边数。
考虑 \(f_u\) 怎么求。

\[f_u = \sum_{u - v} \cfrac{f_v}{d_v} \]

为什么呢?考虑走到 \(v\) 之后,有 \(\cfrac{1}{e_v}\) 的期望会走到这个点。
但是由于 \(n\) 在走到之后就不会再回来了。因此 \(f_n\) 不能计入。可以考虑令 \(f_n = 0\)
\(1\) 刚开始必须走一次,而不是从别的地方走过来的。因此 \(f_1\) 有些特殊,要加 \(1\)

然后得到了 \(n-1\) 个元的线性方程组。考虑高斯消元。(又是一种处理多个变量同时推的式子的方案!)

UVA10288

【题意】
每张彩票上有一个漂亮图案,图案一共 \(n\) 种,如果你集齐了这 \(n\) 种图案就可以兑换大奖。

现在请问,在理想(平均)情况下,你买多少张彩票才能获得大奖的?
【分析】
首先,使用刚刚学过的 min-max 容斥可以发现答案为 \(\sum \limits_{k=1}^n \dbinom{n}{k} \times (-1)^{k-1} \times \cfrac{n}{k}\)

其次,考虑集齐 \(k\) 种之后还需要 \(\cfrac{n}{k}\) 次才能收集到下一种,那么答案为 \(\sum \limits_{k=1}^n \cfrac{n}{k}\)
可以证明这俩等价。

P1654

【题意】
一共有 \(n\) 次操作,每次操作只有成功与失败之分,成功对应 \(1\),失败对应 \(0\)\(n\) 次操作对应为 \(1\) 个长度为 \(n\)\(01\) 串。在这个串中连续的 \(X\)\(1\) 可以贡献 \(X^3\) 的分数,这 \(X\)\(1\) 不能被其他连续的 \(1\) 所包含。

现在给出 \(n\),以及每个操作的成功率,请你输出期望分数,输出四舍五入后保留 \(1\) 位小数。
\(n \le 10^5\)
【分析】
这是一道高次期望问题。为什么强调高次呢?因为期望的线性性:非独立的两个期望不可以相乘,必须转化为相加和数乘的形式。

\(dp_i\) 表示以 \(i\) 结尾的 1 串长度的立方的期望。

考虑 \(dp_i = p_i \times (^3\sqrt {dp_{i - 1}} + 1)^3\)。这个式子并不成立。要转化为什么东西相加呢?

我们回到期望的本质。先观察一下平方的期望是什么意思。

\[E(len_i^2) = \sum\limits_k P(len_i = \sqrt k) \times k \\ E(len_{i+1}^2) = \sum\limits_k P(len_i = \sqrt k) \times (\sqrt k + 1)^2 \times p_{i+1} + 0 \times (1 - p_{i + 1}) \]

为什么呢?考虑 \(E(len_{i+1}^2)\) 应该等于 \(\sum\limits_k P(len_{i+1} = \sqrt k) \times k\)。其中 \(len_{i+1} = len_i + 1\) 的概率是 \(p_{i+1}\),而 \(len_{i+1} = 0\) 的概率是 \((1 - p_{i + 1})\)。于是我们由定义和性质推出了递推式。

期望题一定要首先知道哪一个值是期望,期望打开是什么,而不是凭感觉随便乱推。这道题的题解里我没有使用 \(dp\) 而是使用了 \(E()\) 这个显式表达,就是要不忘初心,才能推的对式子。

接下来,由期望的线性性,我们继续推式子。

\[E(len_{i+1}^2) = \sum\limits_k P(len_i = \sqrt k) \times (\sqrt k + 1)^2 \times p_{i+1} \\ = (\sum\limits_k P(len_i = \sqrt k) \times k + \sum\limits_k P(len_i = \sqrt k) \times 2\sqrt k + \sum\limits_k P(len_i = \sqrt k) \times 1) \times p_{i+1}\\ = (E(len_i^2) + 2E(len_i) + 1) \times p_{i+1} \]

这里注意 \(\sum\limits_k P(len_i = \sqrt k) \times 1) = E(1) = 1\)。(\(\sum P = 1\)

于是我们考虑维护 \(E(len_i)\)\(E(len_i^2)\)。这个式子是这样推出来的,而不是靠玄学。

同样地,三次方的式子我们也可以推,结果是:

\[E(len_i^3) = (E(len_{i-1}^3) + 3E(len_{i-1}^2) + 3E(len_{i-1}) + 1) \times p_{i+1} \]

CF1737E. Ela Goes Hiking

\(n\) 只蚂蚁站成一排,第 \(1\) 只蚂蚁左边和第 \(n\) 只蚂蚁右边各有一个挡板,相邻两只蚂蚁的距离、第 \(1\) 只蚂蚁与左边挡板的距离和第 \(n\) 只蚂蚁与右侧挡板的距离相等。初始时每只蚂蚁重量相等,每只蚂蚁有 \(\frac{1}{2}\) 概率向左运动,\(\frac{1}{2}\) 概率向右运动,每只蚂蚁速度相同。

蚂蚁之间会互相吃,一只蚂蚁吃掉另一只蚂蚁之后质量变为两只蚂蚁的质量之和,并保持速度和方向不变。方向改变当且仅当碰到了挡板,此时立刻改变方向。若两只蚂蚁相遇,重量大的蚂蚁会把重量小的蚂蚁吃掉,如果重量相同,向左运动的蚂蚁会吃掉向右运动的蚂蚁。求对于所有 \(i\le1\le n\),第 \(i\) 只蚂蚁成为最终的存活者的概率对 \(10^9+7\) 取模。

\(n \le 10^6\)


首先考虑一个弱化版的问题:对于一个特定的初始运动方向序列,结果是谁会赢?

考虑一开始向右走的所有蚂蚁都是在送,对于一串 \(R\) 后面一个 \(L\),所有 \(R\) 都会被这个 \(L\) 吃掉。特别地,最后一个如果是 \(R\),也相当于 \(L\)。因此可以把序列缩成一个全部向左走的串,其中每只蚂蚁的重量等于它前面连续 \(R\) 的个数。例如 \(\mathtt{RLRRLLRR} = \mathtt{2312}\)

然后,最前面的两只蚂蚁会打擂台似的一直决出一只继续走下去,例如 \(\mathtt{2312} \rightarrow \mathtt{512} \rightarrow \mathtt{62} \rightarrow \mathtt{8}\)

编号为 \(k\) 的蚂蚁赢了,当且仅当:

  • \(k\) 的方向是 \(\mathtt L\),除非 \(k=n\),此时 \(\mathtt{L/R}\) 都行。
  • 缩起来的序列 \(b\) 中,假设 \(k \rightarrow b_i\),那么 \(b_i \ge \sum \limits_{j=1}^{i-1} b_j\)
  • 对于 \(i' \ge i\),不满足 \(b_{i'} \ge \sum \limits_{j=1}^{{i'}-1} b_j\)

好。那么如今回到原题目来。我们会想到,对于条件 \(2\),要求 \(b_i \ge k-b_i\),那么 \(2b_i \ge k\)。因此,之前 \(\lceil{\cfrac{k-1}{2}\rceil}\) 个数都必须是 \(\mathtt R\)

条件 \(1\) 是好处理的。

那么条件 \(3\) 怎么办?考虑从后向前,减去后面能赢,并且这一位填 \(\mathtt L\) 的就行了。也就是说,对于每一个后面的 \(j\) 赢的概率 \(p_j\),如果这一位可以填 \(\mathtt L\),那么这一位计算权值应该减去 \(\cfrac{p_j}{2}\)。注意是先减再乘,因为只有这样才能过样例先减再乘的话,说明减掉的概率应该在“已经保证之前 \(\lceil{\cfrac{k-1}{2}\rceil}\) 个数都是 \(\mathtt R\) 的全集”下。

进阶期望

期望题可以出的很牛逼,例如 P8967。这时候要灵活运用条件概率条件期望相关的东西。

这里复述一下相关公式和定理。

概念

\(p(A|B),E(A|B)\) 分别表示,当 \(B\) 成立时 \(A\) 的概率、期望。例如扔出两个骰子,考虑 \(x\) 为两个骰子的得数之和,\(y\) 为第一个骰子的得数,那么 \(p(x=7|y=4)=\cfrac{1}{6}\)\(E(x|y=4)=7.5\)

事件 \(A \and B\),因为 \(A,B\) 独立的时候 \(p(A \and B)=p(A) \times P(B)\),所以也会叫做事件 \(AB\)
事件 \(A \or B\),因为 \(A,B\) 独立的时候 \(p(A \or B)=p(A) + P(B)\),所以也会叫做事件 \(A+B\)

公式

条件概率公式:\(p(A|B) = \cfrac{p(AB)}{P(B)}\)

\(p(A|B) = p(A)\) 时,\(A,B\) 独立。(独立的定义)

全期望公式:\(E(A)=\sum_B E(A|B)p(B), \sum_B = \Omega\) 需要保证。

现在我们结合 P8967 这个题来讲讲怎么应用这些公式求解东西。

P8967 追寻

【题意】

\(n\) 维空间中有一个梦想。这梦想坐落在 \((d_1, d_2, \ldots, d_n)\) 的地方。而你从 \((0, 0, \ldots, 0)\) 开始,开启寻梦的旅程。

你的步伐轻缓,每一步只能走一个单位长度。你并不知道你的梦想位于哪里,所以你只能随机选择 \(n\) 个正方向中的一个,然后向这个方向走一步。也就是说,在 \([1, n]\) 中均匀随机选择一个正整数 \(h\),然后,使你在第 \(h\) 维的坐标变成原来的坐标加一。

然而,天有不测风云。在你走每一步的过程中,你会有 \(p = \sum_{i = 1}^k p_i\) 的概率散入天际,并开始一段新的旅程。你会在 \(k\) 个地点中的一个重新开始这段旅程,其中第 \(i\) 个地点的坐标是 \((a_{i,1}, a_{i,2}, \ldots, a_{i,n})\),从这里重新开始的概率为 \(p_i\)

那么,期望下,你离到达这个梦想还需要多少步呢?

由于保证了 \(x_i\) 是随机生成的,可以说明以接近 \(1\) 的概率答案在模意义下存在。事实上,一个当 \(x_i\) 尚不确定时以合理地高的概率给出正确答案的算法足以通过本题,考察复杂的模意义下的有理数的处理不是我们的本意。

  • \(1 \le n \le 100\)\(1 \le k \le 10000\)
  • \(d_i \ge 0\)\(\sum_i d_i \le 10^7\)
  • \(0 \le a_{i, j} \le {10}^7\)
  • \(x_i \ge 1\)\(\sum_i x_i < {10}^8\)。此即保证了 \(p_i > 0\)\(p < 1\)
  • 保证存在一个 \(i \in [1, k]\) 使得对于每个 \(j \in [1, n]\) 均有 \(a_{i,j} \le d_j\)
  • 保证每个 \((a_{i, 1}, a_{i, 2}, \ldots, a_{i, n})\) 作为空间中的点互不相同。
  • 保证每个 \(x_i\) 在所有可能的组合中等概率随机生成。

【分析】

只描述正解。

关键点只有 \(a_i\)。考虑让初始生成点为 \(a_0\)
考虑每段路径都是形如:

\(a_0 \rightsquigarrow a_? \rightsquigarrow a_? ... \rightsquigarrow d\)

考虑\(i\) 次迷失之后,到达终点或第 \(i+1\) 次迷失之前走的期望路程叫做第 \(i\) 段路程。(这个已经很巧妙了,就是采用了分段求解的思想)
那么答案为 \(\sum \limits_{l = 0} ^ {\inf} p(存在第~l~段路程) \times E(第~l~段路程|存在第~l~段路程)\)

考虑分解下去做。

存在第 \(l\) 段路程,也就是迷失了 \(l\) 次,我们先考虑这个东西怎么算。

先考虑当我们在某个关键点重生(条件),走过这一段路程是迷失还是到终点的概率。显然这两个事件的概率和等于 \(1\)(条件概率的好处体现了:缩小概率空间的范围,使得全概率公式能用上)。考虑在第 \(i\) 个关键点重生,那么记 \(q_i\)到终点了(注意,这里只是到终点的概率,虽然路程固定为 \(dis_i\),但是需要满足两个因素:这些步不迷失,并且每一个维度走的是对的步数。注意不迷失不是条件!)的概率。

\[q_i = \cfrac{\dbinom{dis_i}{dis_{i,1}~dis_{i,2}~...~dis_{i,n}}}{n^{dis_i}} \times (1-\sum p_i)^{dis_i} \]

那么又迷失了的概率等于:\(1 - q_i\)

\(r_i = \cfrac{p_i}{\sum p}\)。因此迷失一次之后(条件,比上一个弱,上一个是:在 \(i\) 点重生。但是 \(\sum r_i\) 显然等于 \(\Omega\)。),又迷失一次的概率 \(A\) 等于:

\[\sum \limits_{i=0}^k r_i(1-q_i) \]

那么

\[p(存在第~l~段路程) = (1-q_0) \times A^{l-1} \]

接下来考虑 \(E(第~l~段路程|存在第~l~段路程)\) 怎么求。显然是分两个情况。令 \(t_i\) 表示确定这一次迷失的条件下的期望步数

\[E(第~l~段路程|存在第~l~段路程) = \sum \limits_{i=0}^k p(从这个点出生) \times (q_i dis_i + (1-q_i) t_i) \]

\(l=0\) 的时候,\(p(从这个点出生) = [i = 0]\),否则 \(p(从这个点出生)=r_i\)

只需求 \(t_i\) 即可。

\[t_i = \sum \limits_{step = 1}^ {+\inf} (1-\sum p_i)^{step-1} \sum p_i \times step - [step > dis_i] \times q_i \times (1-\sum p_i)^{step-dis_i-1} \sum p_i\times step \]

利用期望的线性性或者直接手算得到 \(\cfrac{1}{\sum p_i} - q_i \times (\cfrac{1}{\sum p_i} + dis_i)\)。(期望的线性性怎么用,乱走的减去到终点之后乱走一段的。这是高级思想,希望早日会用。)

那么就做完了。最后的式子是:

image

注意多项式定理的组合数预处理可以线性。足够通过本题。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
#define cerr if(false)cerr
#define freopen if(false)freopen
#define watch(x) cerr  << (#x) << ' '<<'i'<<'s'<<' ' << x << endl
void cmax(int &x, int y) {if(x < y) x = y;}
void cmin(int &x, int y) {if(x > y) x = y;}
const int mod = 998244353;
//调不出来给我对拍!
//n 100 k 10000 sum d_i 1e7 p = x / 10^8
int n,k; int d[110]; int a[10010][110]; int p[10010];
int A,B,ans; int q[10010]; int t[10010]; int dis[10010][110];
int jc[10000010], inv[10000010]; const int V = 1e7;
int qpow(int x,int y){
    int ret = 1; 
    while(y) {
        if(y&1) ret=ret*x%mod;
        x=x*x%mod; y>>=1;
    }
    return ret;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    //freopen();
    //freopen();
    //time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    cin>>n>>k; f(i,1,n)cin>>d[i]; int div=qpow(100000000,mod-2);
    f(i,1,k){f(j,1,n){cin>>a[i][j];} cin>>p[i]; p[i]*=div; p[i]%=mod;}
    f(i,0,k)f(j,1,n){dis[i][j]=d[j]-a[i][j];dis[i][0]+=dis[i][j];}
    jc[0]=inv[0]=1;f(i,1,k){p[0]+=p[i]; p[0]%=mod;}int invp=1*qpow(p[0],mod-2);
    f(i,1,V+1) {jc[i]=jc[i-1]*i%mod;} inv[V+1]=qpow(jc[V+1],mod-2);
    for(int i=V;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod;  
    f(i,0,k){
        bool keda=1; f(j,1,n){if(dis[i][j]<0)keda=0;}
        if(keda){q[i]=jc[dis[i][0]];f(j,1,n){q[i]*=inv[dis[i][j]]; q[i]%=mod;}
        q[i]*=qpow(n, dis[i][0] * (mod-2)); q[i]%=mod;//continue;
        q[i]*=qpow(1-p[0]+mod,dis[i][0]); q[i]%=mod; }
        t[i]=(invp-(q[i]*(invp+dis[i][0])%mod)+mod)%mod;
        t[i]*=qpow(1-q[i]+mod,mod-2); t[i]%=mod;
    }//return 0;
    f(i,1,k){A+=(p[i]*qpow(p[0],mod-2)%mod)*(1-q[i]+mod)%mod; A%=mod;}
    f(i,1,k){B+=(p[i]*qpow(p[0],mod-2)%mod)*(q[i]*dis[i][0]%mod+(1-q[i]+mod)*t[i]%mod)%mod;B%=mod;}
    ans=(1-q[0]+mod)*B%mod*qpow(1-A+mod,mod-2)%mod+q[0]*dis[0][0]%mod+(1-q[0]+mod)*t[0]%mod;
    cout<<ans%mod<<endl;
    //time_t finish = clock();
    //cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
}
/*
2023/x/xx
start thinking at h:mm


start coding at 11:17
finish debugging at h:mm
*/
posted @ 2022-08-07 17:15  OIer某罗  阅读(720)  评论(0编辑  收藏  举报