C Easy Counting Problem

C 题题意:给定字符集大小 Σ\Sigma,和每个字符出现次数的下限 {ci}\{c_i\}qq 次询问由 Σ\Sigma 中字符构成的长度为 nn 的字符串的个数。q300q \leq 300n1×107n \leq 1\times 10^7ci5×104\sum c_i \leq 5\times 10^4Σ10|\Sigma| \leq 10

解法:对于 ci\sum c_i 较小的约束条件,可以考虑使用生成函数。本题中由于有次数限制并且对排列计数,因而使用 EGF 指数生成函数。对于第 ii 个字符,其方案对应的 EGF 为

j=ci+xjj!=exj=0ci1xjj!\sum_{j=c_i}^{+\infty} \dfrac{x^j}{j!}=e^x-\sum_{j=0}^{c_i-1} \dfrac{x^j}{j!}

因而对于给定长度的 nn,其答案为 n![xn]i=1Σj=ci+xjj!\displaystyle n![x^n]\prod_{i=1}^{|\Sigma|} \sum_{j=c_i}^{+\infty}\dfrac{x^j}{j!}。考虑如何快速计算:

i=1Σj=ci+xjj!=i=1Σ(exj=0ci1xjj!)=i=0Σ(1)Σieix(jS,S=Σik=0cj1xkk!)\begin{aligned} \prod_{i=1}^{|\Sigma|} \sum_{j=c_i}^{+\infty}\dfrac{x^j}{j!}=&\prod_{i=1}^{|\Sigma|}\left(e^x-\sum_{j=0}^{c_i-1}\dfrac{x^j}{j!}\right)\\ =&\sum_{i=0}^{|\Sigma|}(-1)^{|\Sigma|-i}e^{ix} \left(\prod_{j \in S,|S|=|\Sigma|-i} \sum_{k=0}^{c_j-1}\dfrac{x^k}{k!}\right) \end{aligned}

其中 SΣS \in \Sigma。但是直接计算上式是需要计算 O(Σ2Σ)O(|\Sigma|2^{|\Sigma|}) 次多项式相乘,不够优秀。但是注意到这一形式与 01 背包非常相似,因而使用背包形式的转移:fi,jf_{i,j} 表示截止前 ii 个字符,已经乘了 jj 个多项式的多项式。其转移形式为 fi,jfi1,j+fi1,j1g(j)f_{i,j} \leftarrow f_{i-1,j}+f_{i-1,j-1}g(j),其中 g(j)=k=0cj1xkk!\displaystyle g(j)=\sum_{k=0}^{c_j-1}\dfrac{x^k}{k!}。 这样只需要计算 O(Σ2)\mathcal O(|\Sigma|^2) 次多项式乘法计算。

因而预处理出 fΣ,i,i[0,Σ]\displaystyle f_{|\Sigma|,i},i \in [0,|\Sigma|] 后,容易注意到 fΣ,if_{|\Sigma|,i} 的次数仅为 ci\sum c_i。对于第 nn 项,其答案来源为 e(Σi)xe^{(|\Sigma|-i)x} 的第 nkn-k 项与 fΣ,if_{|\Sigma|,i} 的第 kk 项的系数乘积和,即 k=0cii=0Σ(1)Σin!(Σi)nk(nk)!([xk]fΣ,i)\displaystyle \sum_{k=0}^{\sum c_i}\sum_{i=0}^{|\Sigma|}(-1)^{|\Sigma|-i}\dfrac{n!(|\Sigma|-i)^{n-k}}{(n-k)!} ([x^k]f_{|\Sigma|,i})

因而总的复杂度为 O(Σ2cilogci+qΣci)\mathcal O(|\Sigma|^2 \sum c_i \log \sum c_i+q |\Sigma|\sum c_i)

void Solve() {
    scanf("%d", &m);
    vector<Poly> p(m + 1);
    p[0] = {1};
    for (int i = 0, x; i < m; ++i) {
        scanf("%d", &x);
        Poly c = Poly(ifac, ifac + x);
        for (int j = i + 1; j; --j)
            p[j] += p[j - 1] * c;
    }
    scanf("%d", &q);
    while (q--) {
        int n, ans = 0;
        scanf("%d", &n);
        if (n < p[m].size()) {
            puts("0");
            continue;
        }
        for (int k = 0; k <= m; ++k) {
            int res = 0;
            for (int i = (int)p[k].size() - 1, pw = POW(m - k, n - i); ~i; --i) {
                res = (res + (ll)p[k][i] * pw % P * ifac[n - i]) % P;
                pw = (ll)pw * (m - k) % P;
            }
            inc(ans, k & 1 ? P - res : res);
        }
        printf("%lld\n", (ll)ans * fac[n] % P);
    }
}