luogu2791 幼儿园篮球题 第二类斯大林数(特)卡常NTT

本文深入探讨了一个复杂的组合数学问题,通过详细的数学推导和公式变换,将问题简化并求解。核心内容涉及第二类斯特林数、范德蒙德求和、NTT快速傅里叶变换等高级数学概念,展示了如何高效地处理大规模数据集上的组合计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我真的是***的
原本应该是乘以iLi^LiL
但是因为
iL=∑j=0L\{Lj\}(ij)j! i^L=\sum_{j = 0}^L {L \brace j} \binom{i}{j} j! iL=j=0L{jL}(ji)j!
于是这题显然就是
∑i=0ki(n−mki−i)(mi)∑j=0L\{Lj\}(ij)j! \sum_{i = 0}^{k_i} \binom{n - m}{k_i - i} \binom{m}{i} \sum_{j = 0}^L {L \brace j} \binom{i}{j} j! i=0ki(kiinm)(im)j=0L{jL}(ji)j!
然后期望就是整体除以(nki)\binom{n}{k_i}(kin)
首先交换一下求和顺序
就会变成
∑j=0L\{Lj\}j!∑i=0ki(n−mki−i)(mi)(ij) \sum_{j = 0}^L {L \brace j} j! \sum_{i = 0}^{k_i} \binom{n - m}{k_i - i} \binom{m}{i} \binom{i}{j} j=0L{jL}j!i=0ki(kiinm)(im)(ji)

(mi)(ij)=(mj)(m−ji−j) \binom{m}{i} \binom{i}{j} = \binom{m}{j} \binom{m - j}{i - j} (im)(ji)=(jm)(ijmj)
于是就变成了
∑j=0L\{Lj\}j!(mj)∑i=0ki(n−mki−i)(m−ji−j) \sum_{j = 0}^L {L \brace j} j! \binom{m}{j} \sum_{i = 0}^{k_i} \binom{n - m}{k_i - i} \binom{m - j}{i - j} j=0L{jL}j!(jm)i=0ki(kiinm)(ijmj)
后面发现上面的和为n−jn - jnj,下面的和为ki−jk_i - jkij,都与iii无瓜
于是就是一个范德蒙德求和的形式
就可以变成
∑j=0L\{Lj\}j!(mj)(n−jki−j) \sum_{j = 0}^L {L \brace j} j! \binom{m}{j} \binom{n - j}{k_i - j} j=0L{jL}j!(jm)(kijnj)
前面的第二类斯大林数可以预处理,可以利用
\{Lj\}=∑k=0m(−1)kk!(j−k)L(j−k)! {L \brace j} = \sum_{k = 0} ^ m \frac{(-1)^k}{k!} \frac{(j - k) ^ L}{(j - k)!} {jL}=k=0mk!(1)k(jk)!(jk)L
直接NTT预处理
这题比较卡常
如果直接线性预处理逆元然后做前缀积会T飞。
只有反过来用n!n!n!的逆元乘回来才可以(*的我卡常卡了一早上没想到这里慢破天际)

/* ***********************************************
Author        :BPM136
Created Time  :2019/8/14 9:17:59
File Name     :2791.cpp
************************************************ */

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <ctime>

#define USE_CIN_COUT ios::sync_with_stdio(0)

using namespace std;

typedef long long ll;

namespace fastIO{  
    #define BUF_SIZE 100000  
    #define OUT_SIZE 100000  
    //fread->read  
    bool IOerror=0;  
    inline char nc(){  
		static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE;  
		if (p1==pend){  
			p1=buf; pend=buf+fread(buf,1,BUF_SIZE,stdin);  
			if (pend==p1){IOerror=1;return -1;}  
			//{printf("IO error!\n");system("pause");for (;;);exit(0);}  
		}  
		return *p1++;  
	}  
	inline bool blank(char ch){return ch==32||ch==10||ch==13||ch==9;}  
	inline bool enter(char ch){return ch==10||ch==13;}
	inline void read(int &x){  
		bool sign=0; char ch=nc(); x=0;  
		for (;blank(ch);ch=nc());  
		if (IOerror)return;  
		if (ch==45)sign=1,ch=nc();  
		for (;ch>=48&&ch<=57;ch=nc())x=x*10+ch-48;  
		if (sign)x=-x;  
	}  
	inline void read(ll &x){  
		bool sign=0; char ch=nc(); x=0;  
		for (;blank(ch);ch=nc());  
		if (IOerror)return;  
		if (ch==45)sign=1,ch=nc();  
		for (;ch>=48&&ch<=57;ch=nc())x=x*10+ch-48;  
		if (sign)x=-x;  
	}  
#undef OUT_SIZE  
#undef BUF_SIZE  
}
using fastIO::read;

template<typename T>
void print(T x) {
	static char s[100], *s1; s1 = s;
	if (!x) *s1++ = '0';
	if (x < 0) putchar('-'), x = -x;
	while(x) *s1++ = (x % 10 + '0'), x /= 10;
	while(s1-- != s) putchar(*s1);
}

/////eval多点求值,interpolation插值
//不可传入一个系数为负数的多项式,可能在减法的时候会爆mod
//常数稍微有点大
//G是TT的原根
//NTT前记得getRevRoot

int const TT = 998244353, G = 3;
int const MAXN = 2e7 + 5;
int const MAXM = 3e5 + 5;
int const mod = TT;

int fac[MAXN], inv[MAXN];
int N, M, S, L;

inline int KSM (int a, int k) {
	int ret = 1 % TT;
	for (; k; k >>= 1, a = 1ll * a * a % TT)
		if (k & 1)
			ret = 1ll * ret * a % TT;
	return ret;
}

inline int add (int x, int y) {
    if (x + y >= TT)
        return x + y - TT;
    else
        return x + y;
}

inline int sub (int x, int y) {
    if (x - y >= 0)
        return x - y;
    else
        return x - y + TT;
}

int rev[MAXM << 2], rt[MAXM << 2];

inline void getRevRoot (int n) {
	int m = log(n) / log(2) + 1e-7;
	for (int i = 1; i < n; ++i)
		rev[i] = rev[i >> 1] >> 1 | (i & 1) << (m - 1);
	for (int len = 1, uni; len < n; len <<= 1) {
		uni = KSM(G, (TT ^ 1) / (len << 1));
		rt[len] = 1;
		for (int i = 1; i < len; ++i)
			rt[i + len] = 1ll * rt[i + len - 1] * uni % TT;
	}
}

inline void NTT (int* f, int n) {
	for (int i = 0; i < n; ++i) 
		if (i < rev[i])
			swap(f[i], f[rev[i]]);
	for (int len = 1; len < n; len <<= 1)
		for (int i = 0; i < n; i += len << 1)
			for (int j = 0, x, y; j < len; ++j) {
				x = f[i + j];
				y = 1ll * f[i + j + len] * rt[j + len] % TT;
				f[i + j] = add(x, y);
				f[i + j + len] = sub(x, y);
			}
}

int f[MAXM << 2], g[MAXM << 2];

inline void NTT() {
	int n = 1, m = (L + 1) * 2 - 1, ivn;
	while (n < m)
		n <<= 1;
	ivn = KSM(n, TT - 2);
	getRevRoot(n);
	NTT(f, n);
	NTT(g, n);
	for (int i = 0; i < n; ++i)
		f[i] = 1ll * f[i] * g[i] % TT;
	reverse(f + 1, f + n);
	NTT(f, n);
	for (int i = 0; i < m; ++i)
		f[i] = 1ll * f[i] * ivn % TT;
}

inline void init() {
	inv[0] = inv[1] = fac[0] = fac[1] = 1;
	for (int i = 2; i <= N; ++i) 
		fac[i] = 1ll * fac[i - 1] * i % mod;
	inv[N] = KSM(fac[N], mod - 2);
	for (int i = N - 1; i >= 1; --i)
		inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
	for (int i = 0; i <= L; ++i) {
		f[i] = ((i & 1) ? TT - inv[i] : inv[i]);
		g[i] = 1ll * KSM(i, L) * inv[i] % mod;
	}
	NTT();
}

int main() {
	read(N);
	read(M);
	read(S);
	read(L);
	init();
	int ans;
	int n, m, K;
	while (S--) {
		read(n);
		read(m);
		read(K);
		ans = 0;
		int lim = min(m, min(K, L));
		for (int j = 0; j <= lim; ++j) {
			ans += 1ll * f[j] * inv[m - j] % mod * fac[n - j] % mod * inv[K - j] % mod;
			if (ans >= mod)
				ans -= mod;
		}
		ans = 1ll * ans * fac[K] % mod * inv[n] % mod * fac[m] % mod;
		print(ans);
		putchar('\n');
	}
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值