zoukankan      html  css  js  c++  java
  • luogu P5162 WD与积木 FFT

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

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

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

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

    (displaystyle g_n=sum_{i=1}^nS[n][i] imes 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
    ", F[n]*ksm(G[n], mod - 2, mod) % mod);
    	}
    	return 0;
    }
    
  • 相关阅读:
    朴素贝叶斯分类器实现
    Puppeteer使用
    神经网络常用名词
    Mysql binlog的基本使用和数据库恢复步骤
    webpack之代码分割及页面缓存优化
    webpack之常用loader的配置和使用
    webpack之常用plugin的配置和使用
    第11章 面向对象
    第10章 面向对象
    第9章 模块与包
  • 原文地址:https://www.cnblogs.com/wljss/p/12036854.html
Copyright © 2011-2022 走看看