「解题报告」字符串(7.10,歌唱王国拓展)
题目:给定 \(n\) 个字符串,每次在一个初始为空的字符串后加上一个随机字符,当出现 \(n\) 个字符串中的任意一个时结束。字符串两两不存在包含关系。对于 \(k\in[1, n]\),求在只保留 \([1, k]\) 内的字符串时,结束时字符串的期望长度。\(n \le 300, \sum |S| \le 10^5\)。
令 \(G(x)\) 为进行 \(i\) 次操作后仍未结束的概率,\(F(x)\) 为进行 \(i\) 次操作后结束了的概率。任意进行一步,并减去进行一步后结束的概率,可以得到:
那么所求答案即 \(G(1)\)(也可以由 \(E(X) = \sum_i [x > i]\) 可得)
令:
\(F_i(x)\) 表示最后出现 \(s_i\) 的概率。
那么假设最后出现的是 \(s_i\),我们考虑从某时开始恰好填入 \(s_i\),并容斥掉非第一次出现的概率,那么就有:
直接带入 \(x=1\):
令 \(a_{i, j} = [i = j] + \sum_{k=1}^{l_i - 1} \frac{1}{26^{l_i - k}} [\mathrm{pre}(s_i,k) = \mathrm{suf}(s_j, k)]\),那么我们实际上可以得到 \(n\) 个关于 \(F_i(1), G(1)\) 的方程:
再加上 \(\sum_{i=1}^n F_i(1) = 1\),一共得到了 \(n+1\) 个方程,可以直接消元得出答案。
我们发现,这些方程的系数均与 \(n\) 没有任何关系,只有最后一个方程是有关的。那么我们可以直接对整个矩阵进行消元,在每消完一行之后再将最后一个方程消元,这样就能得出每组方程的解了。
关于能否消出解:我们将 \(F_i(1)\) 作为第 \(i\) 个元,这样对角线上的值都 \(\ge 1\),且其它位置的值都 \(< 1\),于是可以发现 \(a_{i, i}\) 永远都不等于 \(0\),所以可以直接这样做。
\(a_{i, j}\) 是可以直接暴力计算的,每个字符串只会被计算 \(O(n)\) 次,总复杂度是 \(O(n \sum |S|)\) 的。高斯消元部分是 \(O(n^3)\),可以通过。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 305, MAXM = 100005, P = 1000000007;
const __uint128_t HP = 0xffffffff00000001;
int qpow(int a, int b) {
int ans = 1;
while (b) {
if (b & 1) ans = 1ll * ans * a % P;
a = 1ll * a * a % P;
b >>= 1;
}
return ans;
}
int n, m;
int len[MAXN];
int Pow[MAXM], iPow[MAXN];
string s[MAXN];
int a[MAXN][MAXN];
vector<__uint128_t> hs[MAXN];
const int B = 13331;
__uint128_t BASE[MAXM];
__uint128_t piece(int i, int l, int r) { return (hs[i][r] - hs[i][l - 1] * BASE[r - l + 1] % HP + HP) % HP; }
int calc(int i, int j) {
int ret = (i == j);
for (int k = 1; k <= min(len[i] - 1, len[j]); k++) {
if (piece(i, 1, k) == piece(j, len[j] - k + 1, len[j])) {
ret = (ret + iPow[len[i] - k]) % P;
}
}
return ret;
}
int tmp[MAXN];
int main() {
freopen("string.in", "r", stdin);
freopen("string.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
cin >> s[i];
len[i] = s[i].length();
m += len[i];
s[i] = " " + s[i];
hs[i].resize(len[i] + 1);
for (int j = 1; j <= len[i]; j++) {
hs[i][j] = (hs[i][j - 1] * B + s[i][j] - 'a' + 1) % HP;
}
}
BASE[0] = 1;
for (int i = 1; i <= m; i++)
BASE[i] = B * BASE[i - 1] % HP;
Pow[0] = 1;
for (int i = 1; i <= m; i++)
Pow[i] = 26ll * Pow[i - 1] % P;
iPow[m] = qpow(Pow[m], P - 2);
for (int i = m; i >= 1; i--)
iPow[i - 1] = 26ll * iPow[i] % P;
for (int i = 1; i <= n; i++) {
a[i][n + 1] = P - iPow[len[i]];
for (int j = 1; j <= n; j++)
a[i][j] = calc(i, j);
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
int div = (P - 1ll) * a[j][i] % P * qpow(a[i][i], P - 2) % P;
for (int k = i; k <= n + 1; k++)
a[j][k] = (a[j][k] + 1ll * div * a[i][k]) % P;
}
memset(tmp, 0, sizeof tmp);
for (int j = 1; j <= i; j++)
tmp[j] = 1;
for (int j = 1; j <= i; j++) {
int div = (P - 1ll) * tmp[j] % P * qpow(a[j][j], P - 2) % P;
for (int k = 1; k <= n + 1; k++)
tmp[k] = (tmp[k] + 1ll * div * a[j][k]) % P;
}
printf("%d\n", qpow(tmp[n + 1], P - 2));
}
return 0;
}