P2791 幼儿园篮球题
做这道题想说的事有点多呢,前言有点长,可以跳过。
萌新刚刚看了一点斯特林数相关的内容,这是我见到的第二道题。第一道题看了一个上午没看懂题解就放弃了。由于有了第一题铺垫,这题实在是太naive了。
先来说说这题几个值的关注的地方(也是我拿到题的第一反应):
- 最显眼的:有个cxk动图。
- 其次,时限不是普通的1s或1.5s或2s什么的,而是1.11s
- 顺便发现,空间也是三个一样的数组成的:222MB
- 输入是NMSL,疑似骂人
你可能觉得我啥也没说,但是我确实栽在了上面的一个地方/fad
写代码的时候,要卷第二类斯特林数。忽然想起这题曾经在丁爸那里当作作业题出过,一年前的事了,也就是我刚知道FTT那会。当时不会,去问的whk作为一个OIer这名字挺不吉利的awa,还是他qq上教我的qwq。现在他退役了,去搞别的竞赛了。他大概是我们一批人中间最早离开的了/kk。想起现在看不到他了就莫名伤心。上次CSP前问过他,状态不好怎么办。他说他就是这么退役的。现在我的状态过了瓶颈期,多项式救了我。而他没能扛过去。我想他再等几个月说不定就能顺利通过瓶颈,水平飞越,AKIOI。唉,就这样,一个OIer退役了。祝好,希望我们都有光明的前途。
显然答案是:
就是枚举cxk投进了几个球而已。
只看分子。那两个组合数一脸范德蒙德卷积,但是后面带了一个系数没用。
后面那个幂次考虑把它写成第二类斯特林数的形式。
你可能问我为啥想到斯特林数。见前言,我见到过一个比这题恶心的多的题,这个是基础套路,相比之下这题所有的操作都太基础了。
做上面这步阶乘分拆是因为看到了 \(i!\) 可以约掉并且配成新的组合数。
发现把 \(\dbinom{m}{j}\) 提到前面去就可以范德蒙德卷积了。
用一点多项式技巧求预处理出第 \(L\) 行第二类斯特林数,再预处理一下阶乘,对于每一个询问即可 \(O(L)\) (当 \(n,m\) 较小时可以更快)。
总复杂度 \(O(L\log L+SL+N)\) 。
哦对了,说一下我栽在哪里了。空间是222MB,由于习惯预处理了阶乘,阶乘逆元,还有 \([1,n]\) 的逆元,于是空间228MB,MLE了。最后一个不用处理的。
upd:我发现一个都不用预处理,所以空间可以 \(O(L)\) 。
一些补充:
众所周知第二类斯特林数 \(\begin{Bmatrix}n\\m\end{Bmatrix}\) 的组合意义是,\(n\) 个不同的球放进 \(m\) 个相同的盒子且盒子不能为空的方案数
\(m^n\) 的组合意义是 \(n\) 个相同球放进 \(m\) 个不同盒子的方案数
那么等式右边就是,枚举 \(i\) 个非空的盒子,然后把 \(n\) 个球放进去。同时,由于无标号变成了有标号要乘上阶乘。
范德蒙德卷积证明:
提取 \([x^k]\) 系数:
如何求某一行的斯特林数/斯特林数与组合数的关系。
二项式反演得(\(m^n\) 看作一个函数,\(\begin{Bmatrix}n\\i\end{Bmatrix}i!\) 看作一个函数)
第三行可以卷积用,第二行在某些毒瘤题要你暴力展开第二类斯特林数要用。
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define mkp(x,y) make_pair(x,y)
#define pb(x) push_back(x)
#define sz(v) (int)v.size()
typedef long long LL;
typedef double db;
template<class T>bool ckmax(T&x,T y){return x<y?x=y,1:0;}
template<class T>bool ckmin(T&x,T y){return x>y?x=y,1:0;}
#define rep(i,x,y) for(int i=x,i##end=y;i<=i##end;++i)
#define per(i,x,y) for(int i=x,i##end=y;i>=i##end;--i)
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return f?x:-x;
}
const int N=200005;
const int M=N<<2;
const int K=20000005;
#define mod 998244353
int n,m,s,l,sti[N],k,ans;
namespace math{
int fac[K],ifc[K];
inline int qpow(int n,int k){int res=1;for(;k;k>>=1,n=1ll*n*n%mod)if(k&1)res=1ll*n*res%mod;return res;}
inline void fmod(int&x){x-=mod,x+=x>>31&mod;}
inline int comb(int n,int m){return n<m?0:1ll*fac[n]*ifc[m]%mod*ifc[n-m]%mod;}
void initmath(const int&n=K-1){
fac[0]=1;for(int i=1;i<=n;++i)fac[i]=1ll*i*fac[i-1]%mod;
ifc[n]=qpow(fac[n],mod-2);for(int i=n-1;i>=0;--i)ifc[i]=1ll*ifc[i+1]*(i+1)%mod;
}
}
using math::qpow;
using math::fmod;
namespace poly{
int rev[M],lg,lim;
void init_poly(const int&n){
for(lg=0,lim=1;lim<n;lim<<=1,++lg);
for(int i=0;i<lim;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-1));
}
void NTT(int*a,int op){
for(int i=0;i<lim;++i)
if(i>rev[i])swap(a[i],a[rev[i]]);
const int g=op?3:qpow(3,mod-2);
for(int i=1;i<lim;i<<=1){
const int wn=qpow(g,(mod-1)/(i<<1));
for(int j=0;j<lim;j+=i<<1){
int w0=1;
for(int k=0;k<i;++k,w0=1ll*w0*wn%mod){
const int X=a[j|k],Y=1ll*w0*a[i|j|k]%mod;
fmod(a[j|k]=X+Y),fmod(a[i|j|k]=X-Y+mod);
}
}
}
if(op)return;const int ilim=qpow(lim,mod-2);
for(int i=0;i<lim;++i)a[i]=1ll*a[i]*ilim%mod;
}
#define clr(a,n) memset(a,0,sizeof(int)*(n))
#define cpy(a,b,n) memcpy(a,b,sizeof(int)*(n))
void poly_mul(int*f,int*g,int*ans,int n,int m){
static int A[M],B[M];init_poly(n+m);
cpy(A,f,n),clr(A+n,lim-n),NTT(A,1);
cpy(B,g,m),clr(B+m,lim-m),NTT(B,1);
for(int i=0;i<lim;++i)ans[i]=1ll*A[i]*B[i]%mod;
NTT(ans,0);
}
void get_stirling(int n,int*sti){//第n行
static int A[M],B[M];
for(int i=0;i<=n;++i)A[i]=i&1?mod-math::ifc[i]:math::ifc[i],B[i]=1ll*qpow(i,n)*math::ifc[i]%mod;
poly_mul(A,B,A,n+1,n+1);
for(int i=0;i<=n;++i)sti[i]=A[i];
}
}
signed main(){
n=read(),m=read(),s=read(),l=read();
math::initmath(n);
poly::get_stirling(l,sti);
while(s--){
n=read(),m=read(),k=read(),ans=0;
for(int up=min(n,min(m,l)),i=0;i<=up;++i){
fmod(ans+=1ll*math::fac[i]*sti[i]%mod*math::comb(m,i)%mod*math::comb(n-i,k-i)%mod);
}
ans=1ll*ans*qpow(math::comb(n,k),mod-2)%mod;
printf("%d\n",ans);
}
return 0;
}