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 元素选位置,方案数为

\[\binom{n-3k+k}{k} 26^{n-3k}=\binom{n-2k}{k} 26^{n-3k} \]

发现这样计算相比于至少有 \(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(k)=\binom{n-2k}{k} 26^{n-3k}-\sum_{i=k+1}^{\lfloor\frac{n}{3}\rfloor}\binom{i}{k}f(i) \]

将含 \(f\) 的项合并到一起,得到

\[\sum_{i=k}^{\lfloor\frac{n}{3}\rfloor}\binom{i}{k}f(i)=\binom{n-2k}{k} 26^{n-3k} \]

发现可以进行二项式反演,得到

\[f(k)=\sum_{i=k}^{\lfloor\frac{n}{3}\rfloor}(-1)^{i-k}\binom{i}{k}\binom{n-2i}{i} 26^{n-3i} \]

问题转化为快速求解 \(f(0),f(1),...,f(2),f(\lfloor\frac{n}{3}\rfloor)\)

按上面式子朴素计算显然是 \(O(n^2)\),考虑利用卷积优化。

将上面式子进一步化简得到

\[f(k)=\frac{(-1)^k}{k!}\sum_{i=k}^{\lfloor\frac{n}{3}\rfloor}\frac{1}{(i-k)!}\cdot \frac{(-1)^i(n-2i)!26^{n-3i}}{(n-3i)!} \]

\(P_i = \frac{1}{(\lfloor\frac{n}{3}\rfloor-i)!}\)\(Q_i=\frac{(-1)^i(n-2i)!26^{n-3i}}{(n-3i)!}\),则有

\[\begin{aligned} &\sum_{i=k}^{\lfloor\frac{n}{3}\rfloor}\frac{1}{(i-k)!}\cdot \frac{(-1)^i(n-2i)!26^{n-3i}}{(n-3i)!}\\ =&\sum_{i=k}^{\lfloor\frac{n}{3}\rfloor}P_{\lfloor\frac{n}{3}\rfloor-i+k}\cdot Q_i \end{aligned} \]

这样就变成了卷积形式,设 \(R\) 序列为 \(P\)\(Q\) 的卷积,即\(R = P * Q\),也即 \(R_k=\sum_{i+j=k} P_i\cdot Q_j\),则有

\[R_{\lfloor\frac{n}{3}\rfloor+k}=\sum_{i=k}^{\lfloor\frac{n}{3}\rfloor}P_{\lfloor\frac{n}{3}\rfloor-i+k}\cdot Q_i \]

于是

\[f(k)=\frac{(-1)^k}{k!} R_{\lfloor\frac{n}{3}\rfloor+k} \]

卷积复杂度是 \(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;
}
posted @ 2022-07-23 19:56  聆竹听风  阅读(457)  评论(0)    收藏  举报