2022牛客暑期多校第二场 E. Falfa with Substring
2022牛客暑期多校第二场 E. Falfa with Substring
题意
一个长度为 \(n\) 的只由小写字母组成的字符串,计算有多少种情况恰好包含 \(k\) 个 "bit"
子串,给定 \(n\),输出 \(k = 0,1,2,...,n\) 的结果。
思路
我们想算恰好 \(k\) 个 bit
的情况,记为 \(f(k)\)。
显然 \(k > \lfloor\frac{n}{3}\rfloor\) 的情况都是 \(0\),不用考虑。
思路先从算至少有 \(k\) 个 bit
方案数开始。
容易想到钦定 \(k\) 个 bit
,剩下任意,相当于 \(n-3k\) 个随意的元素,\(k\) 个 bit
元素选位置,方案数为
发现这样计算相比于至少有 \(k\) 个 bit
方案数多很多(为什么呢?看下文)。
仔细分析,像上文那样钦定 \(k\) 个 bit
的情况下
这样算会把恰好包含 \(k+1\) 个 bit
的情况算了 \(\binom{k+1}{k}\) 遍。
这样算会把恰好包含 \(k+2\) 个 bit
的情况算了 \(\binom{k+2}{k}\) 遍。
……
这样算会把恰好包含 \(n(n \geq k)\) 个 bit
的情况算了 \(\binom{n}{k}\) 遍。
如果去掉这些贡献,那么就是恰好有 \(k\) 个 bit
的方案数,所以就不用费事计算至少有 \(k\) 个 bit
的方案数,直接就可以得到恰好有 \(k\) 个 bit
的方案数的递推式。
正式地说,就是有如下递推式
将含 \(f\) 的项合并到一起,得到
发现可以进行二项式反演,得到
问题转化为快速求解 \(f(0),f(1),...,f(2),f(\lfloor\frac{n}{3}\rfloor)\)
按上面式子朴素计算显然是 \(O(n^2)\),考虑利用卷积优化。
将上面式子进一步化简得到
令 \(P_i = \frac{1}{(\lfloor\frac{n}{3}\rfloor-i)!}\),\(Q_i=\frac{(-1)^i(n-2i)!26^{n-3i}}{(n-3i)!}\),则有
这样就变成了卷积形式,设 \(R\) 序列为 \(P\) 与 \(Q\) 的卷积,即\(R = P * Q\),也即 \(R_k=\sum_{i+j=k} P_i\cdot Q_j\),则有
于是
卷积复杂度是 \(O(n\log n)\),所以最终复杂度也是 \(O(n\log n)\)。
代码
#include <iostream>
#include <vector>
using namespace std;
typedef long long Lint;
namespace NTT {
typedef int Lint;
typedef long long LLint;
// 2的幂次
const int maxn = (1 << 21) + 10;
const Lint mod = 998244353;
const Lint g = 3;
Lint fpow(Lint a, Lint b, Lint mod) {
Lint res = 1;
for (; b; b >>= 1) {
if (b & 1)
res = (LLint)res * a % mod;
a = (LLint)a * a % mod;
}
return res;
}
inline Lint add(Lint a, Lint b) {
a += b;
return a >= mod ? a - mod : a;
}
inline Lint mul(Lint a, Lint b) {
return (LLint)a * b % mod;
}
int r[maxn];
void cal_r(int n) {
for (int i = 0; i < n; i++) {
r[i] = 0;
r[i] = (i & 1) * (n >> 1) + (r[i >> 1] >> 1);
}
}
void dft(Lint* a, int n, int type) {
for (int i = 0; i < n; i++)
if (i < r[i])
swap(a[i], a[r[i]]);
for (int i = 1; i < n; i <<= 1) {
int p = i << 1;
Lint w = fpow(g, (mod - 1) / p, mod);
if (type == -1)
w = fpow(w, mod - 2, mod);
for (int j = 0; j < n; j += p) {
Lint t = 1;
for (int k = 0; k < i; k++, t = mul(t, w)) {
Lint tmp = mul(a[j + k + i], t);
a[j + k + i] = add(a[j + k], mod - tmp);
a[j + k] = add(a[j + k], tmp);
}
}
}
if (type == -1) {
Lint inv = fpow(n, mod - 2, mod);
for (int i = 0; i < n; i++)
a[i] = mul(a[i], inv);
}
}
Lint p[maxn], q[maxn];
vector<Lint> poly_mul(const vector<Lint>& a, const vector<Lint>& b) {
vector<Lint> res;
int n = a.size(), m = b.size();
res.resize(n + m - 1);
int len = n + m - 1;
int lim = 1;
while (lim < len)
lim <<= 1;
copy(a.begin(), a.end(), p);
fill(p + n, p + lim, 0);
copy(b.begin(), b.end(), q);
fill(q + m, q + lim, 0);
cal_r(lim);
dft(p, lim, 1), dft(q, lim, 1);
for (int i = 0; i < lim; i++)
p[i] = mul(p[i], q[i]);
dft(p, lim, -1);
for (int i = 0; i < n + m - 1; i++)
res[i] = p[i];
return res;
}
}; // namespace NTT
const int maxn = 1e6 + 10;
int n;
int fac[maxn], invfac[maxn], p[maxn], invp[maxn];
void init() {
fac[0] = p[0] = 1;
for (int i = 1; i <= n; i++) {
fac[i] = NTT::mul(fac[i - 1], i);
}
invfac[n] = NTT::fpow(fac[n], NTT::mod - 2, NTT::mod);
for (int i = n - 1; i >= 0; i--) {
invfac[i] = NTT::mul(invfac[i + 1], i + 1);
}
for (int i = 1; i <= n; i++) {
p[i] = NTT::mul(p[i - 1], 26);
}
}
int C(int n, int k) {
if (n < 0 || k < 0 || n < k)
return 0;
return NTT::mul(NTT::mul(fac[n], invfac[k]), invfac[n - k]);
}
void solve() {
cin >> n;
init();
vector<int> P(n / 3 + 1), Q(n / 3 + 1);
for (int i = 0; i <= n / 3; i++) {
P[i] = invfac[n / 3 - i];
Q[i] = NTT::mul(NTT::mul(fac[n - 2 * i], invfac[n - 3 * i]), p[n - 3 * i]);
if (i & 1)
Q[i] = NTT::mod - Q[i];
}
vector<int> res = NTT::poly_mul(P, Q);
for (int i = 0; i <= n / 3; i++) {
int val = NTT::mul(res[n / 3 + i], invfac[i]);
if (i & 1) {
cout << NTT::mod - val << ' ';
} else {
cout << val << ' ';
}
}
for (int i = n / 3 + 1; i <= n; i++) {
cout << 0 << ' ';
}
cout << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int T = 1;
while (T--)
solve();
return 0;
}