概率期望

2025.03.15

P4223 期望逆序对

fenter。

考虑 DP 设 \(k\) 次操作后原来的两个数分别位于 \(x, y\) 的方案数为 \(f_{k, x, y}\)。算贡献是简单的,\(O(kn^4)\)

考虑其实本质不同的位置只有三个:\(x, y, \neq x \neq y\)。于是状态转化为 \(f_{k, 0 / 1 / 2, 0 / 1 / 2}\),复杂度 \(O(kn^2)\)

发现转移和 \(k\) 无关,于是矩阵乘法优化,复杂度 \(O(n^2 \log k)\)

发现还是无法去掉枚举 \(x, y\)\(O(n^2)\),可以 BIT 优化,复杂度 \(O(n \log n \log k)\)

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
#define _all(i, a, b)  for (int i = (a); i >= (b); i -- )
#define ll long long
using namespace std;
const int N = 5e5 + 5, P = 1e9 + 7;
int n, k, Ans, aa[N], fac[N], ifac[N], inv[N];
inline int add(int x, int y) { return x + y >= P ? x + y - P : x + y; }
inline int sub(int x, int y) { return x - y < 0 ? x - y + P : x - y; }
inline int mul(int x, int y) { return 1ll * x * y % P; }
inline void Add(int & x, int y) { x = x + y >= P ? x + y - P : x + y; }
inline void Sub(int & x, int y) { x = x - y < 0 ? x - y + P : x - y; }
inline void Mul(int & x, int y) { x = 1ll * x * y % P; }
inline int qpow(int x, int y) {
	int res = 1;
	for ( ; y; y >>= 1, Mul(x, x))  if (y & 1)  Mul(res, x); return res;
} inline int binom(int x, int y) { return x >= y && x >= 0 && y >= 0 ? mul(fac[x], mul(ifac[y], ifac[x - y])) : 0; }
struct Fenwick_Tree {
	int tr[N];
	inline void clear() { _for (i, 1, n)  tr[i] = 0; }
	inline int lowbit(int x) { return (x & ( - x)); }
	inline void update(int x, int k) { for (int i = x; i <= n; i += lowbit(i))  Add(tr[i], k); }
	inline void update(int l, int r, int k) { if (l <= r)  update(l, k), update(r + 1, - k); }
	inline int query(int x) { int res = 0; for (int i = x; i; i -= lowbit(i))  Add(res, tr[i]); return res; }
	inline int query(int l, int r) { return l <= r ? (l ? query(r) - query(l - 1) : query(r)) : 0; }
} BIT[3];
struct Matrix {
	int n, m, a[7][7];
	inline void unit() { _for (i, 0, n - 1)  _for (j, 0, m - 1)  a[i][j] = i == j;  }
	inline Matrix operator + (Matrix t) {
		Matrix res; res.n = n, res.m = m;
		_for (i, 0, n - 1)  _for (j, 0, m - 1)  res.a[i][j] = add(a[i][j], t.a[i][j]); return res;
	} inline void operator += (Matrix t) { * this = * this + t; }
	inline Matrix operator * (Matrix t) {
		Matrix res; int tmp; res.n = n, res.m = t.m;
		_for (i, 0, res.n - 1)  _for (j, 0, res.m - 1)  res.a[i][j] = 0;
		_for (i, 0, n - 1)  _for (k, 0, m - 1) {
			tmp = a[i][k];
			_for (j, 0, t.m - 1)  Add(res.a[i][j], mul(tmp, t.a[k][j]));
		} return res;
	} inline void operator *= (Matrix t) { * this = * this * t; }
	inline Matrix operator ^ (int t) {
		Matrix res, b; res.n = n, res.m = m, b.n = m, b.m = m; res.unit();
		_for (i, 0, m - 1)  _for (j, 0, m - 1)  b.a[i][j] = a[i][j];
		while (t) { if (t & 1)  res *= b; t >>= 1, b *= b; } return res;
	} inline void operator ^= (int t) { * this = * this ^ t; }
} A, ans;
int main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> k, fac[0] = ifac[0] = ans.n = 1, A.n = A.m = ans.m = 7, memset(ans.a, 0, sizeof(ans.a)), ans.unit(); int a, fa, ga, b, fb, gb = 0, s1 = 0, s2 = 0, s3 = 0;
	_for (i, 1, N - 1)  fac[i] = mul(fac[i - 1], i); ifac[N - 1] = qpow(fac[N - 1], P - 2);
	_all (i, N - 2, 1)  ifac[i] = mul(ifac[i + 1], i + 1), inv[i] = mul(ifac[i], fac[i - 1]); A = (Matrix){7, 7, {{binom(n - 2, 2), 1, n - 2, 0, n - 2, 0, 0}, {1, binom(n - 2, 2), 0, n - 2, 0, n - 2, 0}, {1, 0, add(binom(n - 2, 2), n - 3), 1, 0, 1, n - 3}, {0, 1, 1, add(binom(n - 2, 2), n - 3), 1, 0, n - 3}, {1, 0, 0, 1, add(binom(n - 2, 2), n - 3), 1, n - 3}, {0, 1, 1, 0, 1, add(binom(n - 2, 2), n - 3), n - 3}, {0, 0, 1, 1, 1, 1, add(binom(n - 2, 2), 2 * n - 7)}}}, ans *= A ^ k, Add(Ans, mul(mul(ans.a[0][6], binom(n, 2)), inv[2]));
	_for (i, 1, n)  cin >> aa[i], a = BIT[0].query(aa[i] - 1), b = sub(s1, a), fa = BIT[1].query(aa[i] - 1), fb = sub(s2, fa), ga = BIT[2].query(aa[i] - 1), gb = sub(s3, ga), Add(Ans, mul(ans.a[0][0], b)), Add(Ans, mul(ans.a[0][1], a)), Add(Ans, mul(ans.a[0][2], add(mul(b, mul(i - 2, inv[n - 2])), mul(a, mul(n - i, inv[n - 2]))))), Add(Ans, mul(ans.a[0][3], add(mul(a, mul(i - 2, inv[n - 2])), mul(b, mul(n - i, inv[n - 2]))))), Add(Ans, mul(ans.a[0][4], add(mul(gb, inv[n - 2]), mul(fa, inv[n - 2])))), Add(Ans, mul(ans.a[0][5], add(mul(ga, inv[n - 2]), mul(fb, inv[n - 2])))), Add(s1, 1), Add(s2, i - 1), Add(s3, n - i - 1), BIT[0].update(aa[i], 1), BIT[1].update(aa[i], i - 1), BIT[2].update(aa[i], n - i - 1); cout << Ans << "\n";
	return 0;
}

CF1924E Paper Cutting Again

考虑拆贡献。根据期望的线性性,每一条线 \(x = i(i \in [1, n - 1])\)\(y = j(j \in [1, m - 1])\) 的被操作概率之和 等于 答案。

考虑钦定一条直线并不是最后一次被操作的。我们可以把总共 \(n + m - 2\) 条直线想象成一个排列,依次执行排列的每一项直到面积 \(< k\)。那么一次操作被操作的概率就是 可以让该操作被执行的排列数 除以 总排列数。

对于 \(x = i\),如果此次操作有效那么操作 \(x = j(j < i)\) 一定要放在该操作之后。同时,因为该操作并不是最后一次操作,为了让面积 \(\ge k\),所有操作 \(y > j (j \le \lfloor \frac{k - 1}{i} \rfloor)\) 的操作也都应该放在该操作之后。

所以对于一个 \(x = i\),至少有 \(i - 1 + \min(m, \lfloor \frac{k - 1}{i} \rfloor)\) 个操作要排在他之后,才能使其有效。如何计算这样的排列数量呢?

结论:对于一个排列,要求某一个数字后面至少有 \(k\) 个指定值,这样的排列占总排列的 \(\frac{1}{k + 1}\)

证明是简单的。

考虑这一共 \(k + 1\) 个东西的相对顺序,必须钦定某一个排在最前面。

总排列有 \((k + 1)!\) 种,钦定第一个数后的排列有 \(k!\) 种,占比 \(\frac{k!}{(k+1)!} = \frac{1}{k + 1}\)

直接枚举 \(i\) 计算即可。复杂度 \(O(n + m)\)

但是需要考虑一个条件,如果 \(\min(m, \lfloor \frac{k - 1}{i} \rfloor) = m\),即 \(\lfloor \frac{k - 1}{i} \rfloor \ge m\),从而 \(k > im\),此时 \(i\) 操作肯定不能成为最后一步操作,因为操作了 \(i\) 后面积一定就 \(< k\) 了,要判掉这种情况,不做贡献。

同时统计答案记得加上最后一步操作的 \(1\) 的贡献。

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
#define _all(i, a, b)  for (int i = (a); i >= (b); i -- )
#define ll long long
using namespace std;
const int N = 2e6 + 5, P = 1e9 + 7;
int n, m, ans, fac[N], ifac[N], inv[N]; ll k;
inline int mul(int x, int y) { return 1ll * x * y % P; }
inline void Add(int & x, int y) { x = x + y >= P ? x + y - P : x + y; }
inline void Mul(int & x, int y) { x = 1ll * x * y % P; }
inline int qpow(int x, int y) {
	int res = 1;
	for ( ; y; y >>= 1, Mul(x, x))  if (y & 1)  Mul(res, x); return res;
} inline void solve() {
	cin >> n >> m >> k, ans = 1; int x;
	if (1ll * n * m < k)  return cout << "0\n", void();
	_for (i, 1, n - 1)  if ((x = min((ll)m, (k - 1) / i)) ^ m)  Add(ans, inv[i + x]);
	_for (i, 1, m - 1)  if ((x = min((ll)n, (k - 1) / i)) ^ n)  Add(ans, inv[i + x]); cout << ans << "\n";
}
signed main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	fac[0] = ifac[0] = 1;
	_for (i, 1, N - 1)  fac[i] = mul(fac[i - 1], i); ifac[N - 1] = qpow(fac[N - 1], P - 2);
	_all (i, N - 2, 1)  ifac[i] = mul(ifac[i + 1], i + 1), inv[i] = mul(ifac[i], fac[i - 1]);
	int T; cin >> T; while (T -- )  solve();
	return 0;
}

BZOJ2969 矩形粉刷

根据期望的线性性,期望被刷的格子数等于每个格子被刷的概率之和。进一步转化,每个格子 \(k\) 次操作后被刷的概率 等于 \(1 - (\text{没被刷概率}) ^ k\)

每个格子没被刷的概率是容易算的。复杂度 \(O(nm \log k)\)

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
#define _all(i, a, b)  for (int i = (a); i >= (b); i -- )
#define ll long long
#define ld long double
using namespace std;
const int N = 2e5 + 5;
int k, n, m; ld ans;
inline ld sq(int x) { return (ld)x * x; }
signed main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> k >> n >> m;
	_for (i, 1, n)  _for (j, 1, m)  ans += 1.0 - pow((sq((i - 1) * m) + sq((n - i) * m) + sq(n * (j - 1)) + sq(n * (m - j)) - sq((i - 1) * (j - 1)) - sq((i - 1) * (m - j)) - sq((n - i) * (m - j)) - sq((n - i) * (j - 1))) / sq(n * m), k); cout << fixed << setprecision(0) << ans << "\n";
	return 0;
}

BZOJ4318 OSU!

简单期望。考虑如果长度从 \(x\) 变为 \(x + 1\),分数增加了 \(3x^2 + 3x + 1\)

考虑维护 \(x^2\)\(x\) 的期望,递推是简单的。复杂度 \(O(n)\)

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
#define ld long double
using namespace std;
const int N = 2e5 + 5;
int n; ld x, A[N], B[N], C[N];
signed main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n;
	_for (i, 1, n)  cin >> x, A[i] = (A[i - 1] + 1) * x, B[i] = (B[i - 1] + 2.0 * A[i - 1] + 1) * x, C[i] = C[i - 1] + (3.0 * B[i - 1] + 3.0 * A[i - 1] + 1) * x; cout << fixed << setprecision(1) << C[n] << "\n";
	return 0;
}

CF1842H Tenzing and Random Real Numbers

首先转化:设 \(y_i = x_i - 0.5\),则条件转化为 \(y_i + y_j \le 0\)\(y_i + y_j \ge 0\)\(- 0.5 \le y_i \le 0.5\)

考虑按照 \(|y_i|\) 的绝对值从小到大加入,发现性质(\(|y_i| < |y_j|\)):如果 \(y_i + y_j \ge 0\),则 \(y_j\) 必然 \(\ge 0\);反之同理。

这样就可以状压 DP 了:设 \(f_S\) 表示决策了集合 \(S\) 内的数的合法方案数。转移的时候根据正负号和限制判断合不合法即可。

最后问的概率即为 \(\frac{f_{2^n - 1}}{n!2^n}\)。复杂度 \(O(2^n n)\)

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
using namespace std;
const int N = 21, P = 998244353;
int n, q, ans, e[N][2], f[1 << N];
inline void Add(int & x, int y) { x = x + y >= P ? x + y - P : x + y; }
inline void Mul(int & x, int y) { x = 1ll * x * y % P; }
inline int qpow(int x, int y) {
	int res = 1;
	for ( ; y; y >>= 1, Mul(x, x))  if (y & 1)  Mul(res, x); return res;
} signed main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> q; int op, x, y;
	_for (i, 1, q)  cin >> op >> x >> y, e[x - 1][op] |= 1 << (y - 1), e[y - 1][op] |= 1 << (x - 1); x = qpow(2, P - 2), f[0] = 1;
	_for (S, 1, (1 << n) - 1)  _for (i, 0, n - 1)  if (S & (1 << i))  _for (o, 0, 1)  if ( ! (S & e[i][o ^ 1]))  Add(f[S], f[S ^ (1 << i)]); ans = f[(1 << n) - 1];
	_for (i, 1, n)  Mul(ans, x), Mul(ans, qpow(i, P - 2)); cout << ans << "\n";
	return 0;
}

CF1842G Tenzing and Random Operations

\(\prod_{i = 1}^n (a_i + kv)\)

考虑把每一项的贡献拆开(相当于乘法分配律)。对展开后的每一项进行 DP。设 \(f_{i, j}\) 表示前 \(i\) 个数,陈出来选了 \(j\)\(v\) 的答案。\(f_{i, j} \to f_{i + 1, ?}\) 转移分为三种:

  • \(a_{i + 1}\)\(f_{i + 1, j} \leftarrow f_{i, j} a_i\)

  • 选之前的已经选过的某个 \(v\)\(f_{i + 1, j} \leftarrow f_{i, j} vj\)

  • 选之前未被选过的 \(v\)\(f_{i + 1, j + 1} \leftarrow f_{i, j} v (i + 1) (m - j)\)\(i + 1\) 表示从前面 \(i - 1\) 个位置中挑出一个来做操作,\(m - j\) 表示从剩下的操作中选出一个作为该操作)。

最终答案即为:\(\frac{\sum_{i = 0}^{\min(n, m)} f_{n, i} n^{m - i}}{n^m} = \frac{\sum_{i = 0}^{\min(n, m)} f_{n, i}}{n^i}\)(剩下的 \(v\) 可以随便选位置操作,但不会产生贡献)。

复杂度 \(O(n^2)\)

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
using namespace std;
const int N = 5005 + 5, P = 1e9 + 7;
int n, m, v, ans, a[N], f[N][N];
inline int add(int x, int y) { return x + y >= P ? x + y - P : x + y; }
inline int mul(int x, int y) { return 1ll * x * y % P; }
inline void Add(int & x, int y) { x = x + y >= P ? x + y - P : x + y; }
inline void Mul(int & x, int y) { x = 1ll * x * y % P; }
inline int qpow(int x, int y) {
	int res = 1;
	for ( ; y; y >>= 1, Mul(x, x))  if (y & 1)  Mul(res, x); return res;
} inline int Inv(int x) { return qpow(x, P - 2); }
signed main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> m >> v; int in = Inv(n);
	_for (i, 1, n)  cin >> a[i]; f[0][0] = 1;
	_for (i, 1, n)  _for (j, 0, min(m, i - 1))  Add(f[i][j], mul(f[i - 1][j], add(a[i], mul(j, v)))), Add(f[i][j + 1], mul(mul(f[i - 1][j], v), mul(i, m - j)));
	_for (i, 0, min(n, m))  Add(ans, mul(f[n][i], qpow(in, i))); cout << ans << "\n";
	return 0;
}
posted @ 2025-03-15 16:04  Ray_Wu  阅读(31)  评论(0)    收藏  举报