码迷,mamicode.com
首页 > 其他好文 > 详细

luogu P5162 WD与积木 FFT

时间:2019-12-13 20:02:10      阅读:86      评论:0      收藏:0      [点我收藏+]

标签:括号   怎么   dig   printf   递推   情况   cin   mes   etc   

\(NTT\) 神仙题,强烈建议先自己推一推式子再看题解,顺便orz memset0。

题面没看太懂,大概意思就是求有n个不同的小球,放进 \(1\)\(2\)、$……n $ 个不同的盒子,不可空的情况下,期望用了几个盒子。

按照套路,我们应该分别求出总的方案数\(g_i\)和总共用的盒子数 \(f_i\) ,答案就是 \(f_i\times g_i^{-1}\)

我们先来求总的方案数,设 \(S\) 是第二类斯特林数,则

\(\displaystyle g_n=\sum_{i=1}^nS[n][i]\times i!\)

就是我们枚举用了几个盒子,在乘上 \(i!\) 将盒子看成是不一样的。

但是我们没办法进行下去了,我们考虑一下递推式。

\(\displaystyle g_n=\sum_{i=1}^nC_n^ig_{n-i}\)

含义就是我们枚举第一个盒子里都有哪些球,再乘上剩下的球的方案数 \(g_{n-i}\) 。我们尝试化简一下式子。

\(\displaystyle g_n=\sum_{i=1}^n \frac{n!}{i!(n-i)!}g_{n-i}\)
\(\displaystyle \frac{g_n}{n!}=\sum_{i=1}^n \frac{1}{i!}\frac{g_{n-i}}{(n-i)!}\)

\(\displaystyle G_n=\frac{g_n}{n!}\) \(\displaystyle H_n=\frac{1}{n!}\) ,注意 \(H_0=1,G_0=1\) 那么

\(\displaystyle G_n=\sum_{i=1}^nH_iG_{n-i}\)

有点像分治 \(FFT\)但我们会多项式求逆,我们按照分治 \(FFT\) 的套路来。

\(\displaystyle G=\sum_{i=0}^{\infty}G_ix^i\) \(\displaystyle H=\sum_{i=0}^{\infty}H_ix^i\)

\(\displaystyle G*H=\sum_{i=0}^{\infty}(\sum_{j+k=i}G_jH_k)x^i\)

\(i>0\) 时,\(\displaystyle \sum_{j+k=i}G_jH_k=\sum_{j=0}^iH_jG_{i-j}=\sum_{j=1}^iH_jG_{i-j}+H_0G_i=2G_i\)

\(i=0\) 时,\(G_0H_0=1=2G_0-1\)

所以

\(G*H=2G-1\)

\(G=(2-H)^{-1}\)

多项式求逆一波带走。

我们再来看看怎么求总共的盒子数。

\(\displaystyle f_n=g_n+\sum_{i=1}^nC_n^if_{n-i}\)

含义就是首先我们每一种方案至少有一个盒子,所以我们先加上 \(g_n\) ,对于剩下的 \(n-i\) 个小球,对答案的贡献就是 \(f_{n-i}\) ,因为第一个盒子有 \(C_n^i\) 种选法,所以就是 \(\displaystyle f_n=g_n+\sum_{i=1}^nC_n^if_{n-i}\)

我们再来搞一搞。现将 \(\displaystyle g_n=\sum_{i=1}^nC_n^ig_{n-i}\) 带进去。

\(\displaystyle f_n=\sum_{i=1}^nC_n^ig_{n-i}+\sum_{i=1}^nC_n^if_{n-i}\)

\(\displaystyle \frac{f_n}{n!}=\sum_{i=1}^{n}\frac{1}{i!}\frac{g_{n-i}}{(n-i)!}+\sum_{i=1}^{n}\frac{1}{i!}\frac{f_{n-i}}{(n-i)!}\)

\(\displaystyle F_n=\frac{f_n}{n!}\)

\(\displaystyle F_n=\sum_{i=1}^nH_i(F_{n-i}+G_{n-i})\)

\(\displaystyle F=\sum_{i=0}^{\infty}F_ix^i\)

那么

\(\displaystyle H*(F+G)=\sum_{i=0}^{\infty}(\sum_{j+k=i}((F_k+G_k)H_j))x^i\) (注意看清大括号)

\(i>0\) 时,\(\displaystyle \sum_{j+k=i}(F_k+G_k)H_j=\sum_{j=0}^i(F_{i-j}+G_{i-j})H_{j}=\sum_{j=1}^i(F_{i-j}+G_{i-j})H_{j}+F_i+G_i=2F_i+G_i\)

\(i=0\) 时,\((F_0+G_0)H_0=1=2F_0+G_0\)

所以

\(\displaystyle H*(F+G)=2F+G\)

\(\displaystyle F=\frac{G-GH}{H-2}\)

因为 \(G=(2-H)^{-1}\)

所以 \(G-GH=1-G\)

带进去

\(\displaystyle F=(1-G)(H-2)^{-1}\)

最后答案就是 \(F_nG_n^{-1}\)

推这些式子我竟然推了一下午,我还是太菜了。

#include<iostream>
#include<cstdio>
#define DB double
#define LL long long
using namespace std;
int T, n;
const int N = 400010, M = 100000, mod = 998244353, YY = 3, YYinv = (mod + 1) / 3;
int r[N];
LL jc[N], inv[N], H[N], g[N], G[N], F[N];
inline int read() 
{
    int res = 0; char ch = getchar(); bool XX = false;
    for (; !isdigit(ch); ch = getchar())(ch == '-') && (XX = true);
    for (; isdigit(ch); ch = getchar())res = (res << 3) + (res << 1) + (ch ^ 48);
    return XX ? -res : res;
}
void Write(int x, int opt) 
{
    if (opt && !x)putchar('0');
    if (!x)return;
    Write(x / 10, 0);
    putchar((x - x / 10 * 10) + '0');
}
LL ksm(LL a, LL b, LL mod) 
{
    LL res = 1;
    for (; b; b >>= 1, a = a * a % mod)
        if (b & 1)res = res * a % mod;
    return res;
}
void NTT(LL *A, int lim, int opt) 
{
    for (int i = 0; i < lim; ++i)
        r[i] = (r[i >> 1] >> 1) | ((i & 1) ? (lim >> 1) : 0);
    for (int i = 0; i < lim; ++i)
        if (i < r[i])swap(A[i], A[r[i]]);
    int len;
    LL wn, w, x, y;
    for (int mid = 1; mid < lim; mid <<= 1) 
    {
        len = mid << 1;
        wn = ksm(opt == 1 ? YY : YYinv, (mod - 1) / len, mod);
        for (int j = 0; j < lim; j += len) 
        {
            w = 1;
            for (int k = j; k < j + mid; ++k, w = w * wn % mod) 
            {
                x = A[k]; y = A[k + mid] * w % mod;
                A[k] = (x + y) % mod;
                A[k + mid] = (x - y + mod) % mod;
            }
        }
    }
    if (opt == 1)return;
    int ni = ksm(lim, mod - 2, mod);
    for (int i = 0; i < lim; ++i)A[i] = A[i] * ni % mod;
}
LL c[N];
void INV(int siz, LL *A, LL *B) 
{
    if (siz == 1) 
    {
        B[0] = ksm(A[0], mod - 2, mod);
        return;
    }
    INV((siz + 1) >> 1, A, B);
    int lim = 1;
    while (lim < (siz << 1))lim <<= 1;
    for (int i = 0; i < siz; ++i)c[i] = A[i];
    for (int i = siz; i < lim; ++i)c[i] = 0;
    NTT(c, lim, 1); NTT(B, lim, 1);
    for (int i = 0; i < lim; ++i)B[i] = B[i] * (2 - c[i] * B[i] % mod + mod) % mod;
    NTT(B, lim, -1);
    for (int i = siz; i < lim; ++i)B[i] = 0;
}
void MUL(LL *A, int n, LL *B, int m) 
{
    int lim = 1;
    while (lim < (n + m))lim <<= 1;
    NTT(A, lim, 1); NTT(B, lim, 1);
    for (int i = 0; i < lim; ++i)A[i] = A[i] * B[i] % mod;
    NTT(A, lim, -1);
}
void YYCH() 
{
    inv[0] = inv[1] = jc[0] = jc[1] = 1;
    for (int i = 2; i <= M; ++i)jc[i] = jc[i - 1] * i % mod;
    inv[M] = ksm(jc[M], mod - 2, mod);
    for (int i = M - 1; i >= 1; --i)inv[i] = inv[i + 1] * (i + 1) % mod;
    for (int i = 0; i <= M; ++i) 
    {
        H[i] = inv[i];
        g[i] = mod - H[i];
    }
    g[0] += 2;
    INV(M + 1, g, G);
    for (int i = 0; i <= M; ++i) 
    {
        F[i] = G[i]; g[i] = G[i];
    }
    F[0]--;
    MUL(F, M, g, M);
}
int main() 
{
    YYCH();
    cin >> T;
    while (T--) 
    {
        n = read();
        printf("%lld\n", F[n]*ksm(G[n], mod - 2, mod) % mod);
    }
    return 0;
}

luogu P5162 WD与积木 FFT

标签:括号   怎么   dig   printf   递推   情况   cin   mes   etc   

原文地址:https://www.cnblogs.com/wljss/p/12036854.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!