一、定義
1.第一類斯特林數:
表示方法:S1(n,m)或[mn]
組合意義:指n個點組成m個圓排列的方案數。
遞推求法:S1(n,m)=S1(n-1,m-1)+(n-1)*S1(n-1,m)
快速求法:
∏i=0n−1(x+i)
的第k次項係數就是S1(n,k),所以可用分治fft做到n*log^2或者再推下用倍增fft做到n*log
2.第二類斯特林數
表示方法:S2(n,m)或{mn}
組合意義:指n個點劃分成m個非空集合的方案數。
遞推求法:S2(n,m)=S2(n-1,m-1)+m*S2(n-1,m)
快速求法:考慮枚舉至少i個集合是空的,容斥所求的0個集合非空得:
S2(n,m)=m!1i=0∑m(−1)i∗(im)∗(m−i)n
其中之前乘的階乘分之一是因爲後面式子求出的是在盒子有標號(有序)情況下,這樣才能變成無序集合。
顯然搞一搞就是卷積形式可以直接fft來n*log求
二、應用
參考:(Orz)
https://www.cnblogs.com/acha/p/6444944.html
https://www.cnblogs.com/hchhch233/p/10016543.html
http://yyy.is-programmer.com/posts/202122.html
xk=i=0∑k(ix)∗{ik}∗i!=i=0∑kxi∗{ik}
理解:x種顏色給k個點染色,直接考慮是等式左邊,複雜地考慮用上k種顏色的方案數求和就是等式右邊了。枚舉到k是因爲用的顏色數不會多於點數,而且推式子題目k也一般比x範圍小。
xk=i=0∑k[ik]∗xi
理解:k個點組成圓排列,在爲每個圓排列裏的所有點染一種顏色。等式右邊意義爲暴枚組成了幾個圓排列計算,左邊意義爲:考慮一個個加入點,對於當前點可以染x種顏色任意一種或者放到某個點左邊並和這個點顏色相同,那麼顯然就是xk
而下降冪與上升冪有如下轉化:
xn=(−1)n(−x)nxn=(−1)n(−x)n
所以之前兩個東西可以互相帶來帶去得出一堆等式…(推詳見參考的大佬博客)
斯特林反演:
f(n)=k=0∑n{nk}g(k)⟺g(n)=k=0∑n(−1)n−k[nk]f(k)
之後是一個很重要的東西(推式子方法很有用):
首先要理解"差分"這個東西的真正意義(原來以爲就是一個數組後一項減前一項…),它的別名叫離散微分,就是說把微積分裏的無限小變成1,求導的
dx→0limdxf(x+dx)−f(x)
這個式子裏的dx→0變成dx=1
那麼這樣可導的條件也不是函數連續,而是離散意義下的連續了。
還有一些積分之類符號的變化,可以參見參考的第三篇大佬博客
可以發現這樣能得到
Δxk=k∗xk−1
幾乎就等於原來對xk求導了.
把k用k+1代可以得到xk原函數爲:
k+1xk+1(ps:這裏分母的k+1要看成一個常數而不是關於k的變量)
而根據前面得到冪級數和下降冪之間的通過斯特林數的關係式,發現xk差分也能得到了。
來推下一個較常出現的東西–自然數冪和(先推n-1好化簡一點)
f(n−1)=i=0∑n−1ik=i=0∑n−1j=0∑k{jk}ij=j=0∑k{jk}i=0∑n−1ij=j=0∑k{jk}j+1∑i=0n−1Δij+1=j=0∑k{jk}j+1nj+1f(n)=j=0∑k{jk}j+1(n+1)j+1=(n+1)j=0∑k{jk}j+1nj
複雜度就是瓶頸就在求斯特林數上了。
再來推道cf的題目:
http://codeforces.com/problemset/problem/932/E
i=0∑n(in)ik=i=0∑n(in)j=0∑k{jk}(ji)j!=i=0∑n(n−i)!n!j=0∑k{jk}i−j1=j=0∑k{jk}i=0∑n(n−i)!n!∗(i−j)!1=j=0∑k{jk}∗(n−j)!n!i=0∑n(i−jn−j)=j=0∑k{jk}∗nj∗2n−j
個人覺得剛開始推這種題目難想的就是如何把有關i的項強行化成一個可以快速求的東西。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5050;
const ll mod=1e9+7;
ll S2[N][N],jm[N];
ll n,k;
void Ad(ll &x,ll y)
{if((x+=y)>=mod)x-=mod;}
ll qpow(ll x,ll y)
{
ll res=1;
while(y)
{
if(y&1)res=res*x%mod;
x=x*x%mod,y>>=1;
}
return res;
}
int main()
{
S2[0][0]=1;
for(int i=1;i<N;i++)
{
for(int j=1;j<=i;j++)
{
S2[i][j]=(S2[i-1][j-1]+1LL*j*S2[i-1][j])%mod;
}
}
cin>>n>>k;
jm[0]=1;
for(int i=1;i<=k;i++)
jm[i]=jm[i-1]*(n-i+1)%mod;
ll ans=0;
for(int j=0;j<=min(n,k);j++)
Ad(ans,S2[k][j]*jm[j]%mod*qpow(2,n-j)%mod);
printf("%lld\n",ans);
}