二項式反転(学習ノート)

44739 ワード

q w q qwq機械室最後の二項式反転を学んだ人
二項式反転はfn=Σi=0 n(−1)iとして表すことが知られている× C n i × g i ⟺ g n = ∑ i = 0 n ( − 1 ) i × C n i × f i f_n=\sum_{i=0}^n (-1)^i\times C_n^i\times g_i\Longleftrightarrow g_n=\sum_{i=0}^n(-1)^i\times C_n^i\times f_i fn​=i=0∑n​(−1)i×Cni​×gi​⟺gn​=i=0∑n​(−1)i×Cni​×fi​
極めて対称な式であり,一般的な表現はfn=Σi=0 nCniである.× g i ⟺ g n = ∑ i = 0 n ( − 1 ) n − i × C n i × f i f_n=\sum_{i=0}^n C_n^i\times g_i\Longleftrightarrow g_n=\sum_{i=0}^n(-1)^{n-i}\times C_n^i\times f_i fn​=i=0∑n​Cni​×gi​⟺gn​=i=0∑n​(−1)n−i×Cni​×fi​
ネット上にはたくさんの良い証明書があります.例えば、このブログでは、批判的な証明書がイメージ的だと感じています.私は証明書を書きません(実は証明書を書くのがおっくうです).
ここにはいくつかのやり方とテーマしか書いていません.

ぴったりと至多の転換


もしb l a b l a blabla blablaがちょうどk k k個のb l a b l a blabla blablaがあることを要求するならば、時には計算しにくいことがあって、多くk k個のb l a b l a blabla blablaがあることを求める時とても計算することができて、最も古典的なのは間違いの排出の問題で、間違いの排出の問題はプッシュがあるようで、しかし2項式の反転もすることができます
f i f_とするi fiは適切なシナリオ数を表し、g i g_i giは最大のシナリオ数を表し,g n=Σi=0 nCn i× f i g_n=\sum_{i=0}^n C_n^i\times f_i gn​=i=0∑n​Cni​×fi二項式によるfn=Σi=0 n(−1)n−iの反転× C n i × g i f_n=\sum_{i=0}^n(-1)^{n-i}\times C_n^i\times g_i fn​=i=0∑n​(−1)n−i×Cni​×giそしてg i g_i giは一般的に短い時間で簡単に求めることができ、g gでf f fを求めると答えが得られる.
例題:hdu 1465容易でないシリーズの一つ
誤排問題設定f i f_i fiはi i i個のずれがあることを示し、g i g_i giはi i i個までずらすことを表し、g i=i!g_i=i! gi​=i!,そして、上の式をセットすればいいです.コードは次のとおりです.
#include
#include
#include
#include
#include
#define LL long long
#define N 25
using namespace std;

int n;
LL fac[N],f[N],C[N][N];

inline void prework(){
	fac[0]=1;
	for(int i=1;i<=20;i++) fac[i]=fac[i-1]*i;
	C[0][0]=1;
	for(int i=1;i<=20;i++) C[i][0]=1;
	for(int i=1;i<=20;i++)
		for(int j=1;j<=i;j++)
			C[i][j]=C[i-1][j]+C[i-1][j-1];
	
	for(int i=1;i<=20;i++)
		for(int j=0;j<=i;j++)
			if((i-j)&1) f[i]-=C[i][j]*fac[j];
			else f[i]+=C[i][j]*fac[j];
}

int main(){
	prework();
	while(~scanf("%d",&n)){
		printf("%lld
"
,f[n]); } return 0; }

適切かつ少なくとも変換


同様に時には少なくともk k個のb l a b l a blabla blablaのがもっと良いことを求めます
f i f_とするi fiは適切なシナリオ数を表し、g i g_i giは少なくともシナリオ数を表し、g k=Σi=k n C i k× f i g_k=\sum_{i=k}^nC_i^k\times f_i gk​=i=k∑n​Cik​×fi二項式によるfk=Σi=kn(−1)i−kの反転× C i k × g i f_k=\sum_{i=k}^n(-1)^{i-k}\times C_i^k\times g_i fk​=i=k∑n​(−1)i−k×Cik​×gi​
例題:bzoj 3622:もう何も恐れることはありませんまずa>b a>b a>b多k k個が要求されるので、a>b a>b a>bのはn+k 2frac{n+k}{2}2 n+k個(決して問題を間違えないでください
快適に打つためにf i f_i fiは、少なくともi i i個のa>b a>b a>bのシナリオ数を表すが、これは直接計算できないので、d p dpを考慮して、a,b a,b a,bを小さいものから大きいものに並べ替え、f[i][j]f[i][j]f[i][j]f[i][j]f[i][j]が前i個の少なくともj j j個のa>b a>b a>bを表すシナリオ数を、現在のa[i]a[i]a[i]a[i]a[i]a[i]より小さいb bがいくつあるかを算出するだけで、前に使用したものをさらに減らす.式はf[i][j]=f[i−1][j−1]である.× ( c n t [ i ] − j + 1 ) + f [ i − 1 ] [ j ] f[i][j]=f[i-1][j-1]\times (cnt[i]-j+1)+f[i-1][j] f[i][j]=f[i−1][j−1]×(cnt[i]−j+1)+f[i−1][j]
算出後にf[n][i]f[n][i]f[n][i]f[n][i]を上の式に持ち込んで最終的に適切な答えを計算する注意f[n][i]f[n][i]f[n][i]f[n][i]を使うときは× ( n − i ) !\times (n-i)! ×(n−i)!,他の席は勝手に置いてあるので
コードは次のとおりです.
#include
#include
#include
#include
#include
#define LL long long
#define N 2005
using namespace std;

template<class T>inline void rd(T &x){
	x=0; short f=1; char c=getchar();
	while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	x*=f;
}

const int mod=1e9+9;
int n,k,C[N][N],f[N][N],a[N],b[N],cnt[N],ans,fac[N];

inline void prework(int n){
	C[0][0]=1;
	for(int i=1;i<=n;i++) C[i][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
	fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1LL*fac[i-1]*i%mod;
}

int main(){
	rd(n); rd(k);
	if((n+k)&1) return puts("0"),0;
	prework(n); k=(n+k)>>1;
	for(int i=1;i<=n;i++) rd(a[i]);
	for(int i=1;i<=n;i++) rd(b[i]);
	sort(a+1,a+n+1); sort(b+1,b+n+1);
	int now=0;
	for(int i=1;i<=n;i++){
		while(b[now+1]<a[i] && now<n) now++;
		cnt[i]=now;
	}
	for(int i=0;i<=n;i++) f[i][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			f[i][j]=(1LL*f[i-1][j-1]*max((cnt[i]-j+1),0)%mod+1LL*f[i-1][j])%mod;
	for(int i=k;i<=n;i++){
		f[n][i]=1LL*f[n][i]*fac[n-i]%mod;
		if((i-k)&1) (ans+=mod-1LL*C[i][k]*f[n][i]%mod)%=mod;
		else (ans+=1LL*C[i][k]*f[n][i]%mod)%=mod;
	}
	printf("%d
"
,ans); return 0; }

bzoj 2839:集合カウントはまずf i f_を設定するi fiは、i i i個の要素がちょうど交差していることを示し、g i g_i giは少なくとも、g i=C n iを表す× ( 2 2 n − i − 1 ) g_i=C_n^i\times (2^{2^{n-i}}-1) gi​=Cni​×(22 n−i−1)ここでの意味は,i i i個を必ず選択しなければならないということであり,残りは2 n−i 2^{n−i}2 n−i個の集合を選択しなくてもよいが,いずれも選択しなくてもよい,上式がある.
以上の式を用いて,fk=Σi=kn(−1)i−kを導出した.× C i k × C n i × ( 2 2 n − i − 1 ) f_k=\sum_{i=k}^n(-1)^{i-k}\times C_i^k\times C_n^i\times (2^{2^{n-i}}-1) fk​=i=k∑n​(−1)i−k×Cik​×Cni​×(22 n−i−1)O(n)O(n)O(n)計算でよいが、後の2 n−i 2^{2^{n−i}}22 n−iは指数が&VeryThinSpaceできないため、直接高速べき乗できないようだ.m o d   pbmod p modpは,2 2 t=(2 t−1)2^{2^t}=(2^{2^{t−1})^2 22 t=(22 t−1)2で逆算する.
コードは次のとおりです.
#include
#include
#include
#include
#include
#define N 1000005
#define LL long long
#define int LL
using namespace std;

int n,k,fac[N],inv[N];
LL ans;
const int mod=1000000007;

inline int qpow(int x,int k){
	int ret=1;
	while(k){
		if(k&1) ret=1LL*ret*x%mod;
		x=1LL*x*x%mod; k>>=1;
	} return ret;
}

inline void prework(){
	fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1LL*fac[i-1]*i%mod;
	inv[n]=qpow(fac[n],mod-2); for(int i=n;i;i--) inv[i-1]=1LL*inv[i]*i%mod;
}

inline LL C(int n,int m){return 1LL*fac[n]*inv[m]%mod*inv[n-m]%mod;}

signed main(){
	scanf("%lld%lld",&n,&k); prework();
	int bin=2;
	for(int i=n;i>=k;i--){
		if((i-k)&1) (ans+=mod-C(i,k)*C(n,i)%mod*(bin-1)%mod)%=mod;
		else (ans+=C(i,k)*C(n,i)%mod*(bin-1)%mod)%=mod;
		bin=1LL*bin*bin%mod;
	}
	printf("%lld
"
,(ans+mod)%mod); return 0; }