典中典之第二类斯特林数
第二类斯特林数:将 \(n\) 个元素划分成 \(m\) 个集合,每个集合非空的方案数,记为 \(S(n,m)\)。
\(n^2\) 递推公式:\(S(n,m)=S(n-1,m-1)+m\cdot S(n-1,m)\).
附代码:
s[0][0]=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=i;++j)
s[i][j]=add(s[i-1][j-1],mul(s[i-1][j],j));
第二类斯特林数的卷积公式:
斯特林数转下降幂公式:
又已知一常见组合公式(证明展开即可):
将以上两式合并则有:
对于枚举 \(x\) 的题目,一般只需交换求和号后将后面的组合数用二项式定理合并成 \(n-i\) 次多项式即可,这样可以消掉 \(x\),将时间复杂度由 \(n\) 降到 \(k\)。
例1:CF932E Team Work
从 \(n\) 个物品中挑任意个,挑 \(x\) 个的价值为 \(x^k\)。求所有方案的价值和。\(n\le 10^9, k\le 5000\).
即求
由前文式子可得
交换求和号
改为枚举 \(x-i\)
后半部分求和用二项式定理合并,即为
\(O(k^2)\) 预处理第二类斯特林数后 \(O(k)\) 求解即可。
#include<bits/stdc++.h>
using namespace std;
const int N=5005,Mod=1e9+7;
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline int add(int x, int y)
{
return (x+y>=Mod?x+y-Mod:x+y);
}
inline int po(int x, int y)
{
int r=1;
for(;y;y>>=1,x=mul(x,x)) if(y&1) r=mul(r,x);
return r;
}
int s[N][N],n,m,k;
int main()
{
int n,k; scanf("%d%d",&n,&k);
s[0][0]=1;
for(int i=1;i<=k;++i)
for(int j=1;j<=i;++j)
s[i][j]=add(s[i-1][j-1],mul(s[i-1][j],j));
int ans=0;
for(int i=0,b=1,c=po(2,n),iv=po(2,Mod-2);i<=min(n,k);++i)
{
int t=mul(s[k][i],mul(b,c));
ans=add(ans,t);
b=mul(b,n-i), c=mul(c,iv);
}
printf("%d\n",ans);
}
例2:CF1716F Bags with Balls
有 \(n\) 个盒子,每个盒子里放着编号 \(1\sim m\) 的球。每次从每个盒子里分别抽取 1 个球,令抽到的编号为奇数的球的个数为 \(x\),则价值为 \(x^k\)。求所有可能方案的价值之和。\(T\) 组数据,\(n,m<998244353, k\le 2000\).
即为求
由上文结论易推得
提取一个 \(\lceil\frac{m}{2}\rceil^i\),后面用二项式定理合并,即为 \(m^{n-i}\)。
线性计算即可。复杂度 \(O(k^2+Tk)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=2022,Mod=998244353;
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline int add(int x, int y)
{
return (x+y>=Mod?x+y-Mod:x+y);
}
inline int po(int x, int y)
{
int r=1;
for(;y;y>>=1,x=mul(x,x)) if(y&1) r=mul(r,x);
return r;
}
int s[N][N],n,m,k;
void Init()
{
const int n=2000;
s[0][0]=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=i;++j)
s[i][j]=add(s[i-1][j-1],mul(s[i-1][j],j));
}
int main()
{
Init();
int T; scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&n,&m,&k);
const int o=(m-1)/2+1;
int ans=0;
for(int i=0,b=1,c=1,d=po(m,n),iv=po(m,Mod-2);i<=min(n,k);++i)
{
int t=mul(s[k][i],mul(mul(b,c),d));
ans=add(ans,t);
b=mul(b,n-i), c=mul(c,o), d=mul(d,iv);
}
printf("%d\n",ans);
}
}
CF1287F 与本题基本一致,这里就不写了。
例3. LOJ3300 「联合省选 2020 A」组合数问题
计算
\[\left(\sum_{k=0}^n f(k) \times x^k \times \binom n k\right) \bmod p \]的值。其中 \(n, x, p(\le 10^9)\) 为给定的整数,\(f(k)\) 为给定的一个 \(m(\le \min(n,1000))\) 次多项式 \(f(k) = a_0 + a_1 k + a_2 k^2 + \cdots + a_m k^m\)。
将多项式展开
按前文变换易得
\(O(m^2)\) 预处理+计算即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,x,p,m,s[N][N],d[N];
#define mul(x,y) (1ll*(x)*(y)%p)
inline int add(int x, int y)
{
return (x+y>=p?x+y-p:x+y);
}
inline int po(int x, int y)
{
int r=1;
for(;y;y>>=1,x=mul(x,x)) if(y&1) r=mul(r,x);
return r;
}
int calc(int k)
{
int res=0;
d[k]=po(x+1,n-k);
for(int i=k-1;~i;--i) d[i]=mul(d[i+1],x+1);
for(int i=0,b=1,c=1;i<=min(n,k);++i)
{
int t=mul(s[k][i],mul(mul(b,c),d[i]));
res=add(res,t);
b=mul(b,n-i), c=mul(c,x);
}
return res;
}
int main()
{
scanf("%d%d%d%d",&n,&x,&p,&m);
s[0][0]=1;
for(int i=1;i<=m;++i)
for(int j=1;j<=i;++j)
s[i][j]=add(s[i-1][j-1],mul(s[i-1][j],j));
int ans=0;
for(int i=0;i<=m;++i)
{
int a; scanf("%d",&a);
ans=add(ans,mul(a,calc(i)));
}
printf("%d\n",ans);
}