十二重计数法
配合多项式全家桶食用最佳
\(\text{I}\):球之间互不相同,盒子之间互不相同。
每个球都有 \(m\) 种选择,答案就是 \(m^n\)。
\(\text{II}\):球之间互不相同,盒子之间互不相同,每个盒子至多装一个球。
每个盒子最多装一个,也就是说被一个球选中后不能再被选,也就是 \(m^{\underline{n}}\)。
\(\text{III}\):球之间互不相同,盒子之间互不相同,每个盒子至少装一个球。
容斥,枚举多少个盒子不装球,剩下的盒子随便装球。
发现若盒子之间相同的话就变成了斯特林数,所以答案也为 \({n\brace m}m!\)。
\(\text{IV}\):球之间互不相同,盒子全部相同。
可以理解为 \(n\) 个数划分成 \(m\) 个非空集合,这个就是第二类斯特林数。
顺便讲下斯特林数·行怎么求。
有性质:
可以无脑二项式反演:
还原得:
这是个卷积,设:
那么有:
\(\text{V}\):球之间互不相同,盒子全部相同,每个盒子至多装一个球。
盒子相同,那球随便装,答案就为 \([n\le m]\)。
\(\text{VI}\):球之间互不相同,盒子全部相同,每个盒子至少装一个球。
每个盒子都必须有球,那就是斯特林数了,答案为 \({n\brace m}\)。
\(\text{VII}\):球全部相同,盒子之间互不相同。
考虑插板法,总共 \(n+m-1\) 个位置,选出 \(m-1\) 个位置放插板,答案为 \(\dbinom{n+m-1}{m-1}\)。
\(\text{VIII}\):球全部相同,盒子之间互不相同,每个盒子至多装一个球。
相当于选哪些盒子放球,答案为 \(\dbinom{m}{n}\)。
\(\text{IX}\):球全部相同,盒子之间互不相同,每个盒子至少装一个球。
还是考虑插板法,只不过任何两个板子不能相邻,也就是 \(n-1\) 个位置放,答案为 \(\dbinom{n-1}{m-1}\)。
\(\text{X}\):球全部相同,盒子全部相同。
十二个中相对较难的。
这个可以理解为整数 \(n\) 划分成 \(m\) 个自然数的方案数。
设 \(f_{i,j}\) 表示整数 \(i\) 划分成 \(j\) 个自然数的方案数,有转移:
意思是给所有数 \(+1\),或添加一个数 \(0\)。\(O(n^2)\) 复杂度显然不可过,考虑多项式优化。
设 \(F_i(x)=f_{0,i}x^0+f_{1,i}x^1+f_{2,i}x^2+...+f_{n,i}x^n\)。
那么有转移:
和付公主的背包很像,考虑套路化乘为加:
关于里面式子取 \(\ln\),设 \(G(x)=\ln \frac{1}{1-x^j}\):
所以:
到此为止,可以 \(O(n\sqrt{n})\) 时间内求出 \(\ln F_m(x)\),在巨大常数的 \(O(n\log n)\) 时间内求出 \(F_m(x)\)。
答案就是 \([x^n]F_m(x)\)。
\(\text{XI}\):球全部相同,盒子全部相同,每个盒子至多装一个球。
和 \(\text{V}\) 一样,没区别,答案就是 \([n\le m]\)。
\(\text{XII}\):球全部相同,盒子全部相同,每个盒子至少装一个球。
上面 \(\text{X}\) 的划分数是可以为 \(0\) 的,那这里先给所有数 \(+1\),就变成 \(\text{X}\) 了,答案为 \([x^{n-m}]F_m(x)\)。
代码巨大长,但我还是贴一下(是我的多项式全家桶,但无用的函数删掉了)。
代码:
#include<bits/stdc++.h>
#define int long long
#define pc(x) putchar(x)
#define clr(f,n) memset(f,0,sizeof(int)*n)
#define cpy(f,g,n) memcpy(f,g,sizeof(int)*n)
#define rev(f,n) reverse(f,f+n)
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(int x)
{
if(x<0){x=-x;pc('-');}
if(x>9)write(x/10);
pc(x%10+48);
}
void polywrite(int *f,int n){for(int i=0;i<n;++i)write(f[i]),pc(' ');pc('\n');}//输出
const int mod=998244353;//模数
const int G=3;//原根
const int maxn=800005;//数组大小,最好算算要开多少,像FDT至少8倍
int qpow(int x,int y=mod-2)//普通快速幂
{
int res=1;
while(y)
{
if(y&1)res=res*x%mod;
x=x*x%mod;y>>=1;
}return res;
}
const int invG=qpow(G);
int n,m,f[maxn],g[maxn],tr[maxn],iv[maxn];
void pref(int m){for(int i=0;i<m;++i)tr[i]=(tr[i>>1]>>1)|((i&1)?m>>1:0);}//初始化
void NTT(int *f,bool op,int m)//逆天天
{
pref(m);for(int i=0;i<m;++i)if(i<tr[i])swap(f[i],f[tr[i]]);
for(int p=2;p<=m;p<<=1)
{
int len=p>>1;
int tG=qpow(op?G:invG,(mod-1)/p);
for(int k=0;k<m;k+=p)
{
int buf=1;
for(int i=k;i<k+len;++i)
{
int x=f[i],y=buf*f[i+len]%mod;
f[i]=(x+y)%mod;f[i+len]=(x-y+mod)%mod;
buf=buf*tG%mod;
}
}
}if(!op)for(int i=0,invn=qpow(m);i<m;++i)f[i]=f[i]*invn%mod;
}
void px(int *f,int *g,int m){for(int i=0;i<m;++i)f[i]=f[i]*g[i]%mod;}//点乘
void mul(int *f,int *g,int n,int lim)//乘法
{
static int sav[maxn];
int m;for(m=1;m<(n<<1);m<<=1);
clr(sav,m);cpy(sav,g,m);
NTT(f,1,m);NTT(sav,1,m);
px(f,sav,m);NTT(f,0,m);
clr(f+lim,m-lim);clr(sav,m);
}
void invf(int *f,int n)//逆元
{
int m;for(m=1;m<n;m<<=1);
static int w[maxn],r[maxn],sav[maxn];
r[0]=qpow(f[0]);
for(int p=2;p<=m;p<<=1)
{
int len=p>>1;
cpy(sav,f,p);cpy(w,r,len);
NTT(sav,1,p);NTT(w,1,p);
px(sav,w,p);NTT(sav,0,p);
clr(sav,len);cpy(w,r,p);
NTT(sav,1,p);NTT(w,1,p);
px(sav,w,p);NTT(sav,0,p);
for(int i=len;i<p;++i)r[i]=(mod-sav[i])%mod;
}cpy(f,r,n);clr(sav,m);clr(w,m);clr(r,m);
}
void deriv(int *f,int n)//求导
{
for(int i=1;i<n;++i)
f[i-1]=f[i]*i%mod;
f[n-1]=0;
}
void integ(int *f,int n)//积分
{
iv[1]=1;
for(int i=2;i<=n;++i)
iv[i]=(mod-mod/i)*iv[mod%i]%mod;
for(int i=n-1;i>=1;--i)
f[i]=f[i-1]*iv[i]%mod;
f[0]=0;
}
void lnf(int *f,int n)//ln
{
static int sav[maxn];
cpy(sav,f,n);invf(sav,n);
deriv(f,n);mul(f,sav,n,n);
integ(f,n);clr(sav,n);
}
void expf(int *f,int n)//exp
{
static int sav[maxn],s[maxn];
int m;for(m=1;m<n;m<<=1); s[0]=1;
for(int p=2;p<=m;p<<=1)
{
cpy(sav,s,p<<1);lnf(sav,p);
for(int i=0;i<p;++i)
sav[i]=(f[i]-sav[i]+mod)%mod;
(sav[0]+=1)%=mod;mul(s,sav,p,p);
}cpy(f,s,n);clr(sav,m);clr(s,m);
}
void qpowf(int *f,int n,int k)//快速幂
{
lnf(f,n);
for(int i=0;i<n;++i)f[i]=f[i]*k%mod;
expf(f,n);
}
int fac[400005],ifac[400005],S[200005],F[200005];
void init()
{
fac[0]=iv[1]=1;
for(int i=1;i<=n+m;++i)fac[i]=fac[i-1]*i%mod;
for(int i=2;i<=n+m;++i)iv[i]=(mod-mod/i)*iv[mod%i]%mod;
ifac[n+m]=qpow(fac[n+m]);
for(int i=n+m-1;i>=0;--i)ifac[i]=ifac[i+1]*(i+1)%mod;
}
int C(int x,int y){return x<y?0:fac[x]*ifac[y]%mod*ifac[x-y]%mod;}
void solve4()
{
int res=0;
for(int i=1;i<=m;++i)
res=(res+S[i])%mod;
write(res),pc('\n');
}
signed main()
{
n=read(),m=read();init();
for(int i=0,tmp=1;i<=m;++i)
{
f[i]=qpow(i,n)*ifac[i]%mod;
g[i]=i&1?mod-ifac[i]:ifac[i];
}mul(f,g,m+1,m+1);
for(int i=0;i<=m;++i)S[i]=f[i];
memset(f,0,sizeof f);
for(int i=1;i<=m;++i)
for(int j=i;j<=n;j+=i)
f[j]=(f[j]+iv[j/i])%mod;
expf(f,n+1);for(int i=0;i<=n;++i)F[i]=f[i];
write(qpow(m,n)),pc('\n');
write(fac[m]*ifac[m-n]%mod),pc('\n');
write(S[m]*fac[m]%mod),pc('\n');
solve4();
write(n<=m),pc('\n');
write(S[m]),pc('\n');
write(C(n+m-1,n)),pc('\n');
write(C(m,n)),pc('\n');
write(C(n-1,m-1)),pc('\n');
write(F[n]),pc('\n');
write(n<=m),pc('\n');
write(n<m?0:F[n-m]),pc('\n');
return 0;
}