P5748 集合划分计数题解(组合数学)
xcrr
·
2023-05-07 17:20:21
·
题解
不需要多项式算法的通过方法
这道题原是一个多项式专业对口解决的题,然而看到多项式代码就头疼的蒟蒻也想通过。
第一阶段
其实我们可以用球盒模型相互转化解决这个问题。(如果你想一次了解各类 “球盒” 模型,可以看看 P5824 十二重计数法 这道题,它涵盖本文所有用到的模型)
首先,我们把这个问题化这样一个经典的模型:
将 n 个小球放入 n 个框子中的方案数。框子可以空,小球之间互不相同,框子之间互相相同。
它其实有个专门的名字叫做 bell 数。如何算它呢?我们发现,只要我们枚举装了球的框子的数量 m (对应本题中集合的数量),就可以把问题转化为一个新的模型,将 n 个小球放入 m 个框子中,每个框子都不可以空 的方案数,小球之间互不相同,框子之间互相相同。设对于指定 n,m 上述问题的答案为 S_m^n ,原问题的答案就是 \sum _{i=0}^m S_i^n 。
发现 S 其实就是第二类斯特林数。如果不会可以看看这个题 P1655 小朋友的球 。但是出现了问题,如果用我们熟知的递推法求 S 可以通过 P1655,然而在这道题的数据范围下寄掉了。
所以我们需要继续转化,发现 S 的答案实际上是由 “将 n 个小球放入 m 个框子中,每个框子都不可以空,小球、框子之间都互不相同的方案数” 除以框子的排列方案数(即 m! )得到的。设对于指定 n,m 这个新的模型的答案为 f_m^n 。
同样是枚举空框子个数 $i$,得出关系式
$$
g_m^n=\sum_{i=0}^m C_m^i f_{m-i}^n=\sum_{i=0}^m C_m^i f_i^n
$$
二项式反演得到
$$
f_m^n=\sum_{i=0}^m (-1)^{m-i}C_m^i g_i^n=\sum_{i=0}^m (-1)^{m-i}\frac{m!i^n}{i!(m-i)!}
$$
### 第二阶段
这时在 $n,m\le 10^5$ 的范围下,我们可以算指定 $f$ 的值,也可以算 $S$ 的值,可是原问题还有一个加和,那么就会 T。所以我们看看怎么化式子。因为 $n$ 是没有变过的,以下式子里二维数组的表示我们不带 $n$。
$$
\left.\begin{matrix}
f_m=\sum_{i=0}^m (-1)^{m-i}\frac{m!i^n}{i!(m-i)!}
\\S_m=f_m \cdot\frac{1}{m!}
\\ans=\sum _{i=0}^m S_i
\end{matrix}\right\}
\Rightarrow ans= \sum _{i=0}^m \sum_{j=0}^i \frac{(-1)^{i-j}j^n}{j!(i-j)!}
$$
交换求和号
$$
ans= \sum _{j=0}^m \sum_{i=j}^m \frac{(-1)^{i-j}j^n}{j!(i-j)!}
=\sum _{j=0}^m \frac{j^n}{j!}\sum_{i=j}^m \frac{(-1)^{i-j}}{(i-j)!}
$$
发现后面的和式每项取值只与 $i-j$ 有关,我们换元。
$$
ans=\sum _{j=0}^m \frac{j^n}{j!}\sum_{t=0}^{m-j} \frac{(-1)^t}{t!}
$$
这样就可以了,后面的和式预处理前缀和,算整个的时候只有枚举 $j$ 的复杂度。
### 第三阶段
计算一下复杂度,发现是 $O(T\cdot n\log n)$ 的($\log$ 来自快速幂),还是差点。所以我们要把快速幂的复杂度搞出去。
观察和式,用到快速幂的有分母的求逆元,还有分子的 $j^n$。分母逆元因为阶乘预处理了,它也就可以预处理。分子的 $j^n$ 由于是每次询问需要 $j=0\to m$ 所有的值,可以用线性筛筛出来。
就搞定了!当然常数一定要写小一点!
```cpp
//P5748
#include<bits/stdc++.h>
#define endl '\n'
#define debug cout<<" awa\n";
using ll=long long;
using ull=unsigned long long;
using ld=long double;
using namespace std;
const int mod=998244353;
int read()
{
int x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return x;
}
int n;
const int N=1e5+10;
int fac[N],invfac[N];
int pw(int a,int b)
{
int ret=1;
while(b)
{
if(b&1)ret=1ll*ret*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return ret;
}
void init()
{
fac[0]=1;
for(int i=1;i<=1e5;i++)
fac[i]=1ll*fac[i-1]*i%mod;
for(int i=0;i<=1e5;i++)
invfac[i]=pw(fac[i],mod-2);
}
int b[N],s[N],pm[N],tot;
bool c[N];
inline void solve(){
init();
for(int i=2;i<=1e5;i++)
{
if(!c[i])pm[++tot]=i;
for(int j=1;j<=tot;j++)
{
if(1ll*i*pm[j]>1e5||pm[j]>i)break;
c[i*pm[j]]=1;
if(i%pm[j]==0)break;
}
}
int t;t=read();
while(t--)
{
n=read();
s[0]=0,s[1]=1;
for(int i=2;i<=n;i++)
{
if(!c[i])s[i]=pw(i,n);
for(int j=1;j<=tot;j++)
{
if(1ll*i*pm[j]>n||pm[j]>i)break;
s[i*pm[j]]=1ll*s[i]*s[pm[j]]%mod;
if(i%pm[j]==0)break;
}
}
int ans=0,sum=0;
for(int i=n;i>=0;i--)
{
sum=(1ll*sum+(((n-i)&1)?-1:1)*invfac[n-i])%mod;
ans=(ans+1ll*invfac[i]*s[i]%mod*sum)%mod;
}
printf("%d\n",ans>=0?ans:ans+mod);
}
}
int main()
{
solve();
const int _=0;
return ~~(0^_^0);
}
```