2025.03.13 CW 模拟赛 B. 命运的X

B. 命运的X

思路

最近概率期望的题做的比较多 \((\)虽然还是不咋会\()\), 不难列出一个十分显然的转移式子: \(f_i\) 表示目前已经匹配到第 \(i\) 个数, 匹配到第 \(n\) 个数的期望. 有转移式子

\[f_i = \frac{1}{m}f_{i + 1} + \frac{t}{m} \sum_{j = 1}^t f_{x_j} + \frac{m - t - 1}{m}f_0 + 1 \]

正常来说套一下高斯消元模板就可, 但是这道题要取模, 而且数据范围 \(n \le 2 \times 10^5\).

正难则反, 我们换一种定义. 令 \(f_i\) 表示我们目前已经将 \(1 \sim i - 1\) 匹配完成的期望. 转移有

\[f_i = 1 + \sum_{j = 1}^t \frac{1}{m} (\sum_{k = x_j + 1}^{i - 1} f_k) + \frac{m - t - 1}{m}\sum_{j - 1}^{i - 1} f_j \]

其中 \(x_j\) 表示当前的 \(\rm{border}\). 可以事先对 \(B\) 跑一遍 \(\rm{kmp}\), 转移使用前缀和优化, 时间复杂度 \(\mathcal{O}(n^2)\).

接下来考虑正解.

还是求出 \(B\) 数组的 \(\rm{kmp}\) 数组, 接下来我们维护一个人的集合

  • 加入一个人, 他有 1 元钱.
  • 若某个人有 \(m^i\) 元钱, 则他将用全部的钱赌下一个数是 \(b_{i + 1}\). 如果赌输了, 退出集合; 如果对了, 将钱数 \(\times m\), 变成 \(m^{i + 1}\).

可以发现每个人都有 \(\frac{1}{m}\) 的概率赌对, \(\frac{m - 1}m\) 的概率输光, 所以每个人期望有 1 元钱.

同时所有人总钱数的期望, 就是当前 \(A\) 数组的长度.

考虑终止条件: 当一个人拥有 \(m^n\) 元时, \(B\) 序列被赌出来了, 就可以停止了, 这时所有人的总钱数就是 \(\displaystyle m^n + \sum_{p \in \mathbb{P}} m^p\), 其中 \(\mathbb{P}\) 就是 \(B\)\(\rm{border}\) 集合.

证明一下, 我们假设当前有个人一直赌的是对的, 每个人入场时赌的是一段前缀, 同时这也是一段后缀, 如果有一个人赌错了, 那么他就退出, 同时 \(A\) 序列长度也会加上他所赌的长度, 也就是 \(\rm{border}\) 长度. 换句话说, 每个 \(\rm{border}\) 的长度贡献是独立的, 不会多次计算.

#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>

using namespace std;

constexpr int MOD = 998244353;

int main() {
	cin.tie(nullptr)->sync_with_stdio(false);
	int T;
	cin >> T;
	while (T--) {
		int m, n;
		cin >> m >> n;
		vector<int> vec(n);
		for (int& x : vec) cin >> x;
		// 预处理m的幂次
		vector<long long> pow_m(n + 1);
		pow_m[0] = 1;
		for (int i = 1; i <= n; ++i) {
			pow_m[i] = (pow_m[i - 1] * m) % MOD;
		}
		
		// 构建前缀函数
		vector<int> pi(n, 0);
		for (int i = 1; i < n; ++i) {
			int j = pi[i - 1];
			while (j > 0 && vec[i] != vec[j]) {
				j = pi[j - 1];
			}
			if (vec[i] == vec[j]) {
				++j;
			}
			pi[i] = j;
		}
		
		// 收集所有border长度
		unordered_set<int> borders;
		int k = n > 0 ? pi[n - 1] : 0;
		while (k > 0) {
			borders.insert(k);
			k = pi[k - 1];
		}
		
		// 计算结果
		long long ans = pow_m[n];
		for (int p : borders) {
			ans = (ans + pow_m[p]) % MOD;
		}
		
		cout << ans << endl;
	}
	return 0;
}

结语

其实我也不是很清楚. 等以后再复习.

posted @ 2025-03-14 10:03  Steven1013  阅读(21)  评论(0)    收藏  举报