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;
    }
    
  • 相关阅读:
    PAT (Advanced Level) Practice 1071 Speech Patterns (25分)
    PAT (Advanced Level) Practice 1070 Mooncake (25分)
    PAT (Advanced Level) Practice 1069 The Black Hole of Numbers (20分)
    PAT (Advanced Level) Practice 1074 Reversing Linked List (25分)
    PAT (Advanced Level) Practice 1073 Scientific Notation (20分)
    第一次冲刺个人总结01
    构建之法阅读笔记01
    人月神话阅读笔记01
    四则运算2
    学习进度条(软件工程概论1-8周)
  • 原文地址:https://www.cnblogs.com/wljss/p/12036854.html
Copyright © 2011-2022 走看看