二項式反演
如果 f[m]=∑i=mn(in)g[i]
根據二項式反演:
g[i]=k=i∑n(−1)k−i(ik)f[k]
好像是二項式反演的另一種形式?
正常版本:
f[n]=i=0∑n(in)g[i]g[n]=i=0∑n(−1)n−i(in)f[i]
f[n]=i=0∑n(−1)i(in)g[i]g[n]=i=0∑n(−1)i(in)f[i]
就是在容斥。
斯特林數
-
第一類斯特林數:把n個標號元素排成m個環的方案數。
S1(n,m)=S1(n−1,m−1)+(n−1)⋅S1(n−1,m)有一種組合意義是 n 個元素的排列,有 m 個前綴最大值的方案數。可以自然推出:∑i=0nS1(n,i)=n!
根據遞推式還可以寫出 S1(n) 的生成函數:S1(n)=∏i=0n−1(x+i)。我們用分治 FFT 在 nlog2n 的時間計算某一行。
-
第二類斯特林數:把n個標號元素劃分爲m個相同集合的方案數。
S2(n,m)=S2(n−1,m−1)+m⋅S2(n−1,m)
容斥計算:
S2(n,m)=m!1i=0∑m(−1)i(im)(m−i)n=i=0∑mi !(−1)i⋅(m−i)!(m−i)n可以O(n)算某一項,nlogn 計算某一行。
小技巧
nk=i=0∑nS2(k,i)(in)i!
左邊相當於把k個標號球放進n個標號盒子裏。右邊枚舉放在那些盒子裏,由於盒子有標號所以乘上階乘,再乘上斯特林數。
注意到上式的 i 枚舉到 n,實際上可以枚舉到 k,因爲後面的組合數乘階乘可以看做一個 i 次下降冪,如果 k>n 那麼這個下降冪會變成 0,如果 k<n,那麼 i 在 大於 k 的時候前面的斯特林數就會變成 0。也就是:
nk=i=0∑nS2(k,i)⋅ni↓
我們把這個式子變得和底數沒什麼關係了。有什麼好處呢?如果我們要維護 ∑aik,k 是常數,我們只需要維護 ∑aik↓,最後再 O(k) 計算答案。注意到 xk↓=xk↓+k⋅x(k−1)↓(考慮組合意義),因此 ∑aik↓ 這個式子是可以在 O(k) 的時間變成 ∑(ai+1)k↓。
1
給 n, k 和一個長度爲 n 的數字串。要求對每一個 i, 輸出 ∑j=1iF(j,i)。其中 F(l, r) 爲 l 到 r 內所有數字之和的 k 次方。
n≤50000,k≤100
直接二項式定理複雜度是 O(nk^2),用上面的式子複雜度是 O(nk*10) 的。
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define ld long double
using namespace std;
const int N=100010,mod=1e9+7;
typedef pair <int,int> P;
char s[N];
ll S[110][110],f[110];
int a[N],n,k;
int read()
{
int x=0;char c=getchar(),flag='+';
while(!isdigit(c)) flag=c,c=getchar();
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return flag=='-'?-x:x;
}
void add()
{
for(int i=k;i>0;i--) f[i]=(f[i]+f[i-1]*i)%mod;
}
int main()
{
int T=read();
for(int i=1;i<=100;i++)
{
S[i][1]=1;
for(int j=2;j<=i;j++) S[i][j]=(S[i-1][j-1]+S[i-1][j]*j)%mod;
}
while(T--) //f[i]=sigma{F(j,x)的i次下降冪}
{
memset(f,0,sizeof(f));
n=read(),k=read();
scanf("%s",s+1);
for(int i=1;i<=n;i++) a[i]=s[i]-'0';
for(int i=1;i<=n;i++)
{
f[0]=(f[0]+1)%mod;
for(int j=1;j<=a[i];j++) add();
int ans=0;
for(int j=1;j<=k;j++) ans=(ans+S[k][j]*f[j])%mod;
cout<<ans<<' ';
}
}
return 0;
}
/*by DT_Kang*/
斯特林反演:
f[n]=i=1∑nS2(n,i)⋅g[i]g[n]=i=1∑n(−1)n−i⋅S1(n,i)⋅f[i]
相比二項式反演就是把組合數換成斯特林數。
2
定義兩個結點數相同的圖 G1 與圖 G2 的異或爲一個新的圖 G, 其中如果 (u, v) 在 G1 與G2 中的出現次數之和爲 1, 那麼邊 (u, v) 在 G 中, 否則這條邊不在 G 中. 現在給定 s 個結點數相同的圖 G1…Gs, 設 S = {G1, G2, … , Gs}, 請問 S 有多少個子集的異或爲一個連通圖?
10的貝爾數是可以接受的。
用總方案減去不合法的,我們要算的就是g[i]表示有i個聯通塊的方案數。枚舉了點集劃分以後,不同的點集之間的邊是不能存在的,相同點集的邊隨便選,記爲f。但是這樣沒有保證一個點集的點聯通。因此實際上:f[m]=i=m∑nS2(i,m)⋅g[i]
斯特林反演一下:
g[m]=i=m∑n(−1)i−mS1(i,m)⋅f[i]
只要求g[1],即
g[1]=i=1∑n(−1)i−1S1(i,1)⋅f[i]=i=1∑n(−1)i−1(i−1)!⋅f[i]
那麼問題就變成了如何求f。我們把邊壓成一個longlong,枚舉了集合劃分之後,相當於有一些數,求有多少個子集異或位0。根據線性基的理論,我們求出一個線性基,枚舉線性基外面的任意一個集合,都能在線性基裏找到恰好一個元素,它們的異或爲0。
3
雅禮集訓 方陣
給定一個方陣,每個位置可以填 [1,c] 中的任意一個數,要求填完之後不能有兩行或兩列完全相同,求方案數。n,m≤5000
這種行列都有限制的題目往往是先考慮一邊,也就是讓行都不等價,這樣列可能存在等價的情況。設 f_n 表示 n 列互補等價的方案數,g_n 表示任意的方案數,那麼實際上 gn=∑i=1nfnS2(n,i),反演一下可以得到 fn=∑i=1n(−1)n−ignS1(n,i)。
容易發現,第二類斯特林數總是和等價類劃分之類的問題相關。
4
計算
i=0∑nj=0∑nS(i,j)⋅j!⋅2j
解法1:
第二類斯特林數可以展開:
S(n,m)=m!1i=0∑m(−1)i⋅i!(m−i)!m!⋅(m−i)n=i=0∑mi !(−1)i⋅(m−i)!(m−i)n
是一個卷積。因此我們可以在 O(nlogn) 的時間裏計算 S(n,0),S(n,1),…,S(n,n)。
代回原式:
i=0∑nj=0∑nS(i,j)⋅j!⋅2j=i=0∑nj=0∑nj!⋅2jk=0∑jk !(−1)k⋅(j−k)!(j−k)i=j=0∑nj!⋅2jk=0∑jk !(−1)k⋅(j−k)!∑i=0n(j−k)i
解法2:
考慮第二類斯特林數的意義,是把 n 個不同的球放入 m 個相同盒子裏的方案數。由於盒子非空,因此乘上一個 j! 以後就是放進不同盒子的方案數了。
我們令 gn 表示 ∑i=0nS(n,i)⋅i!⋅2i。如果不考慮 2i,也就是把 n 個不同的球放進任意多個不同的盒子裏的方案數。我們枚舉最後一個盒子放幾個球,可以寫出遞推式:
gn=i=1∑n2⋅(in)⋅gn−in!gn!=i=1∑ni!2⋅(n−i)!gn−i
設:
f(x)=i=0∑∞i!gixih(x)=i=1∑∞i!2xi
則:
f(x)=f(x)∗h(x)+1f(x)=1−h(x)1
爲什麼要加一?因爲 f(x) 和 g(x) 卷出來是沒有常數項的。
然後就是多項式求逆啦。