「解题报告」字符串(7.10,歌唱王国拓展)

发布时间 2023-07-10 17:26:43作者: APJifengc

题目:给定 \(n\) 个字符串,每次在一个初始为空的字符串后加上一个随机字符,当出现 \(n\) 个字符串中的任意一个时结束。字符串两两不存在包含关系。求结束时字符串的期望长度。

\(G(x)\) 为进行 \(i\) 次操作后仍未结束的概率,\(F(x)\) 为进行 \(i\) 次操作后结束了的概率。任意进行一步,并减去进行一步后结束的概率,可以得到:

\[G(x) = xG(x) - F(x) + 1 \implies F'(1) = G(1) \]

那么所求答案即 \(G(1)\)(也可以由 \(E(X) = \sum_i [x > i]\) 可得)

令:

\[F(x) = \sum_{i=1}^n F_i(x) \]

\(F_i(x)\) 表示最后出现 \(s_i\) 的概率。

那么假设最后出现的是 \(s_i\),我们考虑从某时开始恰好填入 \(s_i\),并容斥掉非第一次出现的概率,那么就有:

\[F_i(x) = G(x) \frac{x^{l_i}}{26^{l_i}} - \sum_{j=1}^n \sum_{k=1}^{l_i - 1} F_j(x) \frac{x^{l_i - k}}{26^{l_i - k}} [\mathrm{pre}(s_i,k) = \mathrm{suf}(s_j, k)] \]

直接带入 \(x=1\)

\[F_i(1) = \frac{G(1)}{26^{l_i}} - \sum_{j=1}^n F_j(1)\sum_{k=1}^{l_i - 1} \frac{1}{26^{l_i - k}} [\mathrm{pre}(s_i,k) = \mathrm{suf}(s_j, k)] \]

\(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_{j=1}^n F_j(1) a_{i, j} - \frac{G(1)}{26^{l_i}} = 0 \]

再加上 \(\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;
}