B. 命运的X
思路
最近概率期望的题做的比较多 \((\)虽然还是不咋会\()\), 不难列出一个十分显然的转移式子: \(f_i\) 表示目前已经匹配到第 \(i\) 个数, 匹配到第 \(n\) 个数的期望. 有转移式子
正常来说套一下高斯消元模板就可, 但是这道题要取模, 而且数据范围 \(n \le 2 \times 10^5\).
正难则反, 我们换一种定义. 令 \(f_i\) 表示我们目前已经将 \(1 \sim i - 1\) 匹配完成的期望. 转移有
其中 \(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;
}
结语
其实我也不是很清楚. 等以后再复习.