好难学哦qwq md我还没写完
以dzyo ppt为基础
第一类斯特林数
,也写作,表示将n个数放到m个环里面去。环旋转同构。
递推式很好想。下一个数放在原来的环里还是新开一个。
边界
性质
1.很明显。
2.
数学归纳法证明:
其实就是提了个公因式罢了。
3.
使用置换的思想。首先n个数的排列有n!个。
并且任何一个排列对应了一个置换。而置换会产生环,因为置换和排列一一对应,所以我们只需要求n个数能产生所有可能环树的方案和就可以。
也就是上面的公式。因为,所以把i=0加进去也无可厚非。
第二类斯特林数
这东西就很毒瘤了。
表示将n个元素放到m个集合中。集合不能为空。
继续枚举下一个元素是否新开集合,得到
通项公式
正确的解释是二项式反演,如何反演在下面的下面的下面。通过这个式子:
由于第二类斯特林数可以理解为将n个球刷上x种颜色,我们还可以通过枚举颜色数来计算。
博主写到后面才反应过来这个公式是反演出来的。事实上我根本不明白为什么在下面的式子中集合起初是有顺序的。
警告:下面的解释很感性,可能是错的
如果没有非空的限制,答案是。
现在非空,那我们枚举有k个集合是空的。但很明显,2个集合是空的包含了1个集合是空的。所以容斥一下。
从m个集合中选出k个集合,剩下的集合随便放。但这个时候我们眼中的集合是有顺序的,所以最后要除以m!来消去顺序性。
这样我们可以很快的求出一行的第二类斯特林数。因为这是一个卷积形式。
emmm因为我太菜不太会所以请教了一下巨神,这里来说一下怎么卷积,,
原式等于
设,然后将a和b都生成函数表示,卷积起来的第m项就是原式。
或者说,卷起来的每一项系数对应了这一行二类斯特林的一项。
//5395 Luogu
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}return cnt*f;
}
const int mod=167772161;
int n;
int fac[200003],ifac[200003];
int A[1000003],b[1000003],limit,l,r[1000003];
int ksm(int x,int y){
int sum=1;
while(y){
if(y&1)sum=sum*x%mod;y>>=1;x=x*x%mod;
}return sum;
}
void getl(int len){
limit=1,l=0;
while(limit<=len)limit<<=1,l++;
for(int i=0;i<limit;i++){
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
}
}
void NTT(int *a,int len,int inv){
for(int i=0;i<len;i++){
if(i<r[i])swap(a[i],a[r[i]]);
}
for(int mid=1;mid<len;mid<<=1){
int wn=ksm(3,(mod-1)/(mid<<1));
for(int i=0;i<len;i+=mid*2){
int omega=1;
for(int j=0;j<mid;j++,omega=omega*wn%mod){
int x=a[i+j],y=a[i+j+mid]*omega%mod;
a[i+j]=(x+y)%mod;a[i+j+mid]=(x-y+mod)%mod;
}
}
}if(inv==1)return;
reverse(a+1,a+len);int gu=ksm(len,mod-2);
for(int i=0;i<len;i++)a[i]=a[i]*gu%mod;
}
void solve(){
for(int i=0;i<=n;i++)A[i]=((i&1)?(mod-1):1)*ifac[i]%mod,b[i]=ifac[i]*ksm(i,n)%mod;
//for(int i=0;i<=n;i++)cout<<A[i]<<" ";cout<<endl;for(int i=0;i<=n;i++)cout<<b[i]<<" ";cout<<endl;
getl(n<<1);
NTT(A,limit,1);NTT(b,limit,1);
//for(int i=0;i<limit;i++)cout<<A[i]<<" ";cout<<endl;for(int i=0;i<limit;i++)cout<<b[i]<<" ";cout<<endl;
for(int i=0;i<limit;i++)A[i]=A[i]*b[i]%mod;
NTT(A,limit,-1);
for(int i=0;i<=n;i++)cout<<A[i]<<" ";
}
signed main(){
n=in;fac[0]=ifac[0]=1;
for(int i=1;i<=n;i++){
fac[i]=fac[i-1]*i%mod;
}
ifac[n]=ksm(fac[n],mod-2);//cout<<fac[n]<<" "<<ifac[n]<<endl;
for(int i=n-1;i>=1;i--)ifac[i]=ifac[i+1]*(i+1)%mod;
//for(int i=0;i<=n;i++)cout<<fac[i]<<" ";cout<<endl;
//for(int i=0;i<=n;i++)cout<<ifac[i]<<" ";cout<<endl;
solve();
return 0;
}
好久没打NTT,忘了两个东西卷积起来项数要变大,,getl的时候爆锅了。
转化幂
其实这个用的更多。
数学归纳法证明
最关键的就是把那个多出来的x拆成k和(x-k)然后分别乘进去。这种思想我称为下降幂补阶。最后直接套递推式。
与此同时,也有上升幂的说法。
这次我终于可以自己推证明了,,
然后通过第一类斯特林还可以将上升\下降幂改成幂次方。
,
仍然数学归纳
你可以发现乘x进去相当于一起向高次挪一位,就变成了递推公式的第一项。(n-1)乘进去跟递推公式第二项是一样的。latex太难写我就不打证明了。
由此我们还可以得出第一类斯特林的一个快速求法。
令生成函数
根据我们刚刚说的递推公式转换得到
所以
分治FFT做已经很快了。如果还是rilong可以倍增FFT。虽然我不会,看dzyo ppt去。(md洛谷卡log方只能倍增我还不会)
与此同时。由于第二类斯特林数可以理解为将n个球刷上x种颜色,我们还可以通过枚举颜色数来计算。
在x种颜色中选k个。然后消去环之间的顺序性,最后n个球随便选择。
这个式子有组合数,所以我们可以二项式反演。诶,我还没学二项式反演耶。
于是我去学了二项式反演然后把博客搬了进来。
所以
所以,,继续按照刚刚的那种分类方式做卷积,nlogn。
哎呀我忘了写斯特林反演TAT那还例个什么题
一个小小的例题 TCO14 Wildcard CountTables
一个n*m的矩阵,c种颜色,每个格子可以染色,但任何两行或者两列不能完全相同。求方案数。
我们可以先确定行不相同,然后再删掉列相同。
首先行不相同的方案数为,即随便染色,然后每行依次-1。
然后设表示i列互不相同的方案数。
那我们用总方案悄悄地容斥一下就可以得到了
那就随便预处理阶乘啊之类的就星了
另一个小小的例题 FJOI2016建筑师
不管怎么样,肯定会看见最高的那个。
那去掉最高的那个,我们左边看到(x-1)个,右边(y-1)个。
而那些被挡住的小楼的不同也会导致方案不同。
所以左边有(x-1)个环,右边(y-1)个。共(x+y-2)个环,其中有(x-1)个放左边。一类斯特林枚举最高楼位置即可。
// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define int long long
using namespace std;
int s[50003][205],c[205][205];
int t,n,a,b;
const int p=1e9+7;
signed main(){
s[0][0]=c[0][0]=1;
for(int i=1;i<=50000;i++){
for(int j=1;j<=200;j++){
s[i][j]=(s[i-1][j-1]+(i-1)*(s[i-1][j])%p)%p;
}
}
for(int i=1;i<=202;i++){
c[i][0]=1;
for(int j=1;j<=202;j++){
c[i][j]=(c[i-1][j]+c[i-1][j-1])%p;
}
}
cin>>t;
while(t--){
cin>>n>>a>>b;
int ans=(s[n-1][a+b-2]*c[a+b-2][a-1])%p;
cout<<ans<<endl;
}
return 0;
}
还有一个小小的例题 TJOI\HEOI2016 求和
求
两种做法。我选择第一种。
1.通项公式强行使用。
因为j>i时,二类为0,所以改成n无妨。
最后一步是个等比数列求和。
然后上卷积就行了
2.观察式子发现,需要求的是将i个数划分成j个集合,每个集合贡献2,带标号,总贡献为贡献之积。而n个数的总贡献为积贡献之和。
设
则
即枚举最后一个集合的元素个数,组合数一下,贡献2乘上以前的。
分治FFT即可。然而我还是喜欢第一种。
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}return cnt*f;
}
const int mod=998244353;
int n;
int fac[1000003],ifac[1000003];
int ksm(int a,int b){
int sum=1;
while(b){
//cout<<sum<<" "<<a<<endl;
if(b&1)sum=sum*a%mod;a=a*a%mod;b>>=1;
}return sum;
}
int a[1000003],b[1000003];
int r[1000003],limit,l;
void getl(int len){
limit=1;l=0;
while(limit<len)limit<<=1,l++;
for(int i=0;i<limit;i++){
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
}
}int x;int ans=0;
void NTT(int *a,int len,int inv){
for(int i=0;i<len;i++){
if(i<r[i])swap(a[i],a[r[i]]);
}
for(int mid=1;mid<len;mid<<=1){
int wn=ksm(3,(mod-1)/(mid<<1));
for(int i=0;i<len;i+=mid*2){
int omega=1;
for(int j=0;j<mid;j++,omega=omega*wn%mod){
int x=a[i+j],y=a[i+j+mid]*omega%mod;
a[i+j]=(x+y)%mod;a[i+j+mid]=(x-y+mod)%mod;
}
}
}if(inv==1)return;
reverse(a+1,a+len);int gu=ksm(len,mod-2);
for(int i=0;i<len;i++)a[i]=a[i]*gu%mod;
}int jz[1000003];
int query(int n){
for(int i=0;i<=n;i++){
a[i]=(i&1)?mod-1:1;(a[i]*=ifac[i])%=mod;b[i]=((i-1)<0)?(i+mod-1):(i-1);b[i]=ksm(b[i],mod-2)*ifac[i]%mod;
x=ksm(i,n+1)+mod-1;b[i]=b[i]*x%mod;b[i]=(b[i]+mod)%mod;
}b[1]=n+1;
// for(int i=0;i<=n;i++){
// cout<<a[i]<<" ";
// }cout<<endl;for(int i=0;i<=n;i++)cout<<b[i]<<" ";cout<<endl;
getl(n<<1);
NTT(a,limit,1);NTT(b,limit,1);
for(int i=0;i<limit;i++)a[i]=a[i]*b[i]%mod;
NTT(a,limit,-1);
for(int i=0;i<=n;i++){
ans=(ans+jz[i]*fac[i]%mod*a[i]%mod)%mod;
}return ans;
}
signed main(){
n=in;
fac[0]=1;jz[0]=1;
for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod,jz[i]=jz[i-1]*2%mod;
ifac[n]=ksm(fac[n],mod-2);
for(int i=n-1;i>=1;i--)ifac[i]=ifac[i+1]*(i+1)%mod;ifac[0]=1;
//for(int i=0;i<=n;i++)cout<<ifac[i]<<" ";cout<<endl;
cout<<query(n);
return 0;
}
这里仍然有一个小小的例题 HDU4625
求所有点到一个点距离的k次方和。k500,n50000。对每个点执行。
直接算会炸,因为从一个m次方推导到另一个m次方需要展开二项式爆算。
但如果另辟蹊径走下降幂维护,最后转回去,会好很多。
首先证明一个结论
这也就是说,维护下降幂答案的复杂度本身是O(1)的。而维护k个下降幂无非就是k次,总复杂度=O(nk);
子树答案可以递归,但父亲答案计算需要容斥。我们详细说明一下。
假设我们已经计算好了到父亲的答案。那么所有从不属于u子树过来的dis到u都是到父亲dis+1。
根据公式为
可是不幸的是,本来应该直接到u的dis,先去了fa,又回到了u。所以我们实质上多算了一个又少算了一个
把这个多算的根据公式展开就是
那么我们dfs两次。一次做子树,另一次加上父亲答案并根据上式容斥,最后下降幂二类斯特林转幂就行。
顺带一提,容斥一阶下降幂和零阶下降幂的公式自己去想。(其实就是把不存在的项甩掉罢了)
#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}return cnt*f;
}
int first[50003],nxt[100003],to[100003],tot;
void add(int a,int b){
nxt[++tot]=first[a];first[a]=tot;to[tot]=b;
}
int f[50003][503];
int t,n,k;
int ans;
const int mod=1e4+7;
int s[503][503];
void dfs1(int u,int faa){
f[u][0]=1;
for(int i=1;i<=k;i++)f[u][i]=0;
for(int i=first[u];i;i=nxt[i]){
int v=to[i];if(v==faa)continue;
dfs1(v,u);f[u][0]=(f[u][0]+f[v][0])%mod;
for(int j=1;j<=k;j++){
f[u][j]=(f[u][j]+f[v][j]+j*f[v][j-1]%mod)%mod;
}
}
}
void dfs2(int u,int fa){
if(fa){
for(int j=k;j>=2;j--){
f[u][j]=(f[fa][j]+f[fa][j-1]*j%mod-2*j*f[u][j-1]%mod-j*(j-1)%mod*f[u][j-2]%mod);(f[u][j]+=10*mod)%=mod;
}
f[u][1]=f[fa][1]+f[fa][0]-2*f[u][0];(f[u][1]+=mod*10)%=mod;
f[u][0]=f[fa][0];
}
for(int i=first[u];i;i=nxt[i]){
int v=to[i];if(v!=fa)dfs2(v,u);
}
}
int main(){
t=in;
s[0][0]=1;
for(int i=1;i<=500;i++){
for(int j=1;j<=i;j++){
s[i][j]=(s[i-1][j-1]+s[i-1][j]*j%mod)%mod;
}
}//cout<<"#";
while(t--){
n=in;k=in;ans=0;
memset(first,0,sizeof(first));tot=0;
for(int i=1;i<n;i++){
int u=in;int v=in;add(u,v);add(v,u);
}
dfs1(1,0);
//for(int i=1;i<=n;i++)cout<<f[i][2]<<endl;
dfs2(1,0);
// for(int i=1;i<=n;i++){
// for(int j=0;j<=k;j++)cout<<f[i][j]<<" ";cout<<endl;
// }
for(int i=1;i<=n;i++){ans=0;
for(int j=0;j<=k;j++){
ans=(ans+s[k][j]*f[i][j]%mod)%mod;
}printf("%d\n",ans);
}
}
return 0;
}
今儿就写到这里,,好日龙啊这东西