zhengrui26NOIP十连测day5-T3
https://www.zhengruioi.com/problem/3695?cid=2210

时间复杂度要求:\(\mathcal O(n|\Sigma|)\),其中\(|\Sigma|\) 是字符集大小。
首先,我们考虑转化。
我们假设现在要求字符 \(T\) 的答案。
我们记 \(t_{a,b}\) 表示有多少个字符串 \(S\) 满足 \(S<T\) 的充要条件是 \(a<b\),其中 \(a,b\) 是两个字符。
并记
则一个单词的排名
这样有点不太妙,我们把它改成
其中 \(C\) 是一个不重要常数,而此处 \(t\) 的含义与原来不太一样,具体看后面。
这个为什么可以呢?
我们考虑 \(I_{a,b}=1-I_{b,a}\)。
原来那个式子等价于
所以我们新的 \(t_{a,b}\) 的定义就是原来的 \(t_{a,b}\) 减去一个 \(t_{b,a}\),我们在统计 \(t_{a,b}\) 的时候若 \(a>b\),就给 \(t_{b,a}\) 减去要加的的数就行。
这个常数 \(C\) 为什么不重要呢?
我们考虑
所以一个常数对答案没有影响。
所以我们要求的就是 \(E(S^2)-E(S)^2\),其中
为什么题面管那个东西叫方差?
众所周知,方差的定义是 \(Var(X)=E((X-E(X))^2)\)。
我们进行推导。
我们先看括号里的东西。
我们对这个东西取期望。
就是
注意到,\(E(x)\) 是一个常数。
所以这个东西也就是
得证。
得到了这个结论,我们就可以使用一个好优化的 \(Var(X)\) 的形式。
我们令 \(S=\sum_{i}X_i\),这里的 \(i\) 对应到之前提到的 \(S\) 就是一个满足 \(a<b\) 的 \((a,b)\) 点对,而 \(X_i\) 也就是 \(t_{a,b}I_{a,b}\)。
ps:下文提到的任意一个点对 \((x,y)\) 都附带 \(x<y\) 的条件。
我们这里先用 \(X_i\) 进行代替。
我们要算 \(Var(S)\),还是先看括号里的东西
我们对这个东西取期望,就是
我们把后面这一大坨 \(E((X_i-E(X_i))(X_j-E(X_j)))\) 叫做 \(Cross(X_i,X_j)\),中文名是协方差。
有一个结论,\(Cross(A,B)=E(AB)-E(A)E(B)\)。
为什么呢?
我们还是先考虑括号里面的东西,即 \((X_i-E(X_i))(X_j-E(X_j))\)。
这个东西就是
我们给它取期望,就是
我们再次注意到 \(E(X_i)\) 和 \(E(X_j)\) 都是常数。
因此这个东西就是
即
所以
我们把 \(i=(a,b)\),\(j=(c,d)\) 回带。
此时发现枚举 \(i<j\) 相当于是枚举两个不同的点对 \((a,b),(c,d)\),且要求不重复枚举 \((a,b),(c,d)\) 和 \((c,d),(a,b)\)。
则
其中 \(P(a,b,c,d)\) 表示上面所说的:枚举两个不同的点对 \((a,b),(c,d)\),且要求不重复枚举枚举 \((a,b),(c,d)\) 和 \((c,d),(a,b)\)。
因为 \(t_{a,b},t_{c,d}\) 是常数,所以我们可以提出来。
注意到 \(Var\) 是二次的,所以 \(t_{a,b}\) 提出来是 \(t_{a,b}^2\)。
右面那个东西的系数也可以提出来,得到:
对于左边那一项,\(Var(I_{a,b})=E(I_{a,b}^2)-E{I_{a,b}}^2\),注意到 \(I\) 要么是 \(0\),要么是 \(1\),因此 \(I_{a,b}^2=I_{a,b}\)。
又因为在随机排列中,\(a<b\) 的概率是 \(\frac{1}{2}\),因此 \(Var(I_{a,b})=\frac{1}{2}-\frac{1}{4}=\frac{1}{4}\)、
对于 \(Var(S)\) 的右面那一大坨东西,我们发现当 \(a,b,c,d\) 互不相同的时候,\(I_{a,b}\) 和 I_{c,d}$ 彼此独立,因此此时 \(E(I_{a,b}I_{c,d})=E(I_{a,b})E(I_{c,d})\),\(Cross\) 的贡献为 \(0\)。
因此我们右边相当于枚举三个字母 \(a,b,c\),而此时点对的形状就是这三种:
考虑第一个形状。
列举全排列:
发现 \(a<b\) 且 \(a<c\) 的概率是 \(\frac{1}{3}\)。
因此 \(E(I_{a,b}I_{a,c})-E(I_{a,b})E(I_{a,c})=\frac{1}{3}-\frac{1}{4}=\frac{1}{12}\)。
而第二个形状的贡献则是 \(\frac{1}{6}-\frac{1}{4}=-\frac{1}{12}\)。
第三个形状的贡献是 \(\frac{1}{3}-\frac{1}{4}=\frac{1}{12}\)。
此时那坨东西还是不好处理。
怎么办?
上文中,我们只对满足 \(a<b\) 的 \(t_{a,b}\) 处理了贡献,而这里我们定义 \(t_{b,a}=-t_{a,b},a<b\)。
并直接给出右面那个东西的等价形式:
不证明了,好好想想。
则
我们发现这个很不好看。
我们便定义
我们再次直接给出等价形式
如何处理 \(t\) 和 \(\delta\)?
直接在trie上跳就行。
代码:
点击查看代码
#include <iostream>
#include <string>
using std::cin;
using std::cout;
const int N = 2e5 + 10;
const int mod = 998244353;
int pow(int x, int k)
{
int ret = 1;
for (; k; x = 1ll * x * x % mod, k >>= 1)
{
if (k & 1)
ret = 1ll * ret * x % mod;
}
return ret;
}
int tot;
int mul;
int sum;
std::string s;
int id[N];
int d[26];
int siz[N];
int ans[N];
int t[26][26];
int son[N][26];
void ins(std::string s, int i)
{
int now = 0;
for (auto c : s)
{
int x = c - 'a';
if (!son[now][x])
son[now][x] = ++tot;
now = son[now][x];
}
id[now] = i;
}
void chd(int x, int k)
{
sum -= 1ll * d[x] * d[x] % mod;
if (sum < 0)
sum += mod;
d[x] += k;
if (d[x] >= mod)
d[x] -= mod;
if (d[x] < 0)
d[x] += mod;
sum += 1ll * d[x] * d[x] % mod;
if (sum >= mod)
sum -= mod;
}
void cht(int x, int y, int k)
{
chd(x, k);
if (x < y)
{
sum -= 1ll * t[x][y] * t[x][y] % mod;
if (sum < 0)
sum += mod;
}
t[x][y] += k;
if (t[x][y] >= mod)
t[x][y] -= mod;
if (t[x][y] < 0)
t[x][y] += mod;
if (x < y)
{
sum += 1ll * t[x][y] * t[x][y] % mod;
if (sum >= mod)
sum -= mod;
}
}
void dfs(int x)
{
siz[x] = !!(id[x]);
for (int i = 0; i < 26; ++i)
{
int s = son[x][i];
if (!s)
continue;
dfs(s);
siz[x] += siz[s];
}
}
void get(int x)
{
if (id[x])
ans[id[x]] = 1ll * sum * mul % mod;
for (int i = 0; i < 26; ++i)
{
int s = son[x][i];
if (!s)
continue;
for (int j = 0; j < i; ++j)
{
int ss = son[x][j];
if (ss)
{
cht(j, i, siz[ss]);
cht(i, j, -siz[ss]);
}
}
for (int j = i + 1; j < 26; ++j)
{
int ss = son[x][j];
if (ss)
{
cht(i, j, -siz[ss]);
cht(j, i, siz[ss]);
}
}
get(s);
for (int j = 0; j < i; ++j)
{
int ss = son[x][j];
if (ss)
{
cht(j, i, -siz[ss]);
cht(i, j, siz[ss]);
}
}
for (int j = i + 1; j < 26; ++j)
{
int ss = son[x][j];
if (ss)
{
cht(i, j, siz[ss]);
cht(j, i, -siz[ss]);
}
}
}
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
{
cin >> s;
ins(s, i);
}
mul = pow(12, mod - 2);
dfs(0);
get(0);
for (int i = 1; i <= n; ++i)
cout << ans[i] << '\n';
return 0;
}
时间复杂度 \(\mathcal O(|\Sigma|n)\)

浙公网安备 33010602011771号