CF1864H Asterism Stream【概率 DP,矩阵优化】

发布时间 2024-01-08 19:00:06作者: came11ia

给定一变量,初始为 \(1\),每次等概率随机进行以下两种操作之一:

  • \(x\) 加一。
  • \(x\) 乘二。

求期望多少次操作之后 \(x\)\(\ge n\)

\(T\) 组数据,\(T\le 100\)\(n\le 10^{18}\)


对着 aw 老师的题解学的,感觉太深刻。

下文中所有表示下标的分式均为下取整。

\(f_i\) 表示 \(i\) 被遍历到的期望次数,则答案为 \(\sum\limits_{i = 1} ^ {n - 1} f_i\)。容易写出转移:\(f_i = \begin{cases}\frac{1}{2}(f_{i-1}+f_{i/2})&2 \mid i\\\frac{1}{2}f_{i-1}&2 \nmid i\end{cases}\)

注意到每个状态的前驱较少,尝试用矩阵描述转移。考虑向量 \(A_i\) 里需要记录哪些信息。首先 \(f_i\) 必须有。当 \(i\) 为偶数时,还要知道 \(f_{i/2}\)。这样,如果 \(i\) 是偶数但不是 \(4\) 的倍数,那么从 \(A_{i - 1}\) 转移到 \(A_{i}\) 是容易的:\(f_i\) 的前驱是 \(f_{i - 1}\)\(f_{i / 2}\),而 \(\frac{i}{2}\) 是奇数,所以 \(f_{i / 2} = \frac 1 2 f_{i / 2 - 1} = \frac 1 2 f_{(i - 1) / 2}\),可由 \(A_{i - 1}\) 记录的 \(f_{(i - 1) / 2}\) 直接求出。对于 \(f_{i / 2}\) 同理。但如果 \(i\)\(4\) 的倍数,那就还要继续递归下去,因为 \(f_{i / 2} = \frac 1 2 (f_{i / 2 - 1} + f_{i / 4})\)

综上,我们考虑在 \(A_i\) 中记录所有 \(f_{i / 2 ^ k}\),其中 \(k\) 为整数且 \(0\leq k\leq \log_2 \max n\)。为了防止对于边界的讨论,根据 \(f_1 = 1\)\(f_1 = \frac 1 2 f_0\),不妨认为 \(f_0 = 2\)。这样信息就封闭了。

根据转移过程容易发现,转移矩阵只和当前数 \(i\) 所包含的 \(2\) 的幂次 \(\nu_2(i)\) 有关。一个直观的理解是把 \(i\) 写成二进制形式,那么实际上 \(\nu_2(i)\) 就是 \(i\) 最低的 \(1\) 对应的位置,这样容易证明 \(i\)\(i-1\) 只有最低的 \(\nu_2(i)\) 位不同。设当前元素所包含的 \(2\) 的幂次为 \(j\) 的转移矩阵为 \(F_j\)。我们知道关于 \(2\) 的幂的一个简单性质:对于 \(n < 2 ^ k\)\(\nu_2(n) = \nu_2(n + 2 ^ k)\),这样就可以类似矩阵快速幂那样递推了。设 \(H_j = \prod\limits_{i=1}^{2^j} F_i\)\(G_j = \prod\limits_{i=1}^{2^{j+1}-1} F_i\),则有 \(H_0 = G_0=F_0\),且 \(H_i= G_{i-1} \times F_i\)\(G_i = H_i \times G_{i-1}\)

最后我们需要知道 \(\sum\limits_{i = 1} ^ {n - 1} f_i\),把这个也放进 \(A_i\) 里一起转移就行了。时间复杂度 \(\mathcal{O}((T + \log n) \log^3n)\)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 60;
constexpr int mod = 998244353, iv = (mod + 1) >> 1;
bool Mbe;
struct mat {
	ull a[N + 1][N + 1];
	void init() { memset(a, 0, sizeof(a)); }
	mat operator * (const mat &x) const {
		mat y; y.init();
		for (int i = 0; i <= N; i++) {
			for (int k = 0; k <= i; k++) {
				for (int j = 0; j <= k; j++) y.a[i][j] += a[i][k] * x.a[k][j];
				if ((k & 15) == 15 || k == i) {
					for (int j = 0; j <= i; j++) y.a[i][j] %= mod; 
				}
			}
		}
		return y;
	}
	mat operator / (const mat &x) const {
		mat y; y.init();
		for (int k = 0; k <= N; k++) {
			for (int j = 0; j <= k; j++) y.a[0][j] += a[0][k] * x.a[k][j];
			if ((k & 15) == 15 || k == N) {
				for (int j = 0; j <= N; j++) y.a[0][j] %= mod;
			}
		}
		return y;
	}
} f[N], g[N], h[N], I;
LL n;
void solve() { 
	cin >> n;
	mat ans = I;
	for (int i = 59; ~i; i--) {
		if (n >> i & 1) ans = ans / h[i];
	}
	cout << (ans.a[0][0] + mod - 2) % mod << "\n";
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	for (int i = 0; i < N; i++) {
		f[i].a[0][0] = 1;
		f[i].a[1][0] = 1;
		for (int j = 0; j <= i; j++) {
			int coef = iv;
			for (int k = j; k < i; k++) {
				f[i].a[k + 1][j + 1] = coef;
				coef = 1LL * coef * iv % mod;
			}
			f[i].a[i + 1][j + 1] = coef;
		}
		for (int j = i + 1; j < N; j++) {
			f[i].a[j + 1][j + 1] = 1;
		}
	}
	h[0] = g[0] = f[0];
	for (int i = 1; i < N; i++) {
		h[i] = g[i - 1] * f[i];
		g[i] = h[i] * g[i - 1];
	}
	for (int i = 1; i <= N; i++) I.a[0][i] = 2;
	int t = 1;
	cin >> t;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}