洛谷P4859已經沒有什麼好害怕的了-二項式反演

題目

題目鏈接

題目大意:有兩組長度爲n的數ab,組間數字互不相同。兩組數字之間兩兩配對,不重不漏,給定一個數字k,求滿足a中數字>b 比 b中數字>a 的組數多k的分組方案數

題目分析

1.問題轉化

因爲組間數字兩兩不同,所以其實就是告訴你了有多少組a比b大。

令新k=n+k2\frac{n+k}{2},那麼如果n+k是一個奇數答案爲0.

現在就變成了詢問a>ba>b的數目爲k的方案數

2.動態規劃

首先對ab數組排序

直接求正好爲k的不太好求,因爲我們考慮從前往後給a配對。

1.如果當前數字匹配了一個比它小的,那很好辦。

2.如果比他大,還要考慮被選的這個b組數對後面的影響。

第一種情況很好算,令dpi,jdp_{i,j}表示前i個數字選了j個數字是比b組數字大的

那麼轉移方程:dpi,j=dpi1,j+(lasti(j1))dpi1,j1dp_{i,j}=dp_{i-1,j}+(last_{i}-(j-1))*dp_{i-1,j-1}

其中lastilast_{i}表示b數組裏面有這麼多個數字比aia_i

因爲我們不考慮第二種,也就是說除去前面那j1j-1組a>b的b被匹配了,其餘的lasti(j1)last_{i}-(j-1)組b>a的我們沒有考慮,默認是都可以選。

3.二項式反演

那麼我們在n組數字裏面確定了j個是a>b,剩下的就隨便排列,所以是dpn,i(ni)!dp_{n,i}*(n-i)!

這個代表a>b的組數至少是i個的情況。

我們令g(i)表示恰好有i組數字是a>b

g(k)=dpn,k(nk)!g(k)=dp_{n,k}*(n-k)!

那麼f(i)=k=inCkig(k)f(i)=\sum_{k=i}^{n}C_{k}^{i}g(k)

根據二項式反演:g(i)=k=in(1)kiCkif(k)g(i)=\sum_{k=i}^{n}{(-1)^{k-i}C_{k}^{i}f(k)}

二項式反演的基本介紹和證明見:二項式反演

因此我們要求的就是g(k)

更新:

更新前沒講明白這個f的表達式怎麼來的,很多博文也沒說清楚,非常抱歉

反演之前:f(i)=k=inCkig(k)f(i)=\sum_{k=i}^{n}C_{k}^{i}g(k)

這個組合數很多人可能不理解。

在這裏插入圖片描述

畫個圖輔助理解:我們看藍色點,表示恰好3個。那麼綠、黃、紅都代表至少兩個。

那麼在f函數裏面,恰好3個的一種方案:藍色點,在至少兩個裏面出現了三次。

因爲我們回到“至少”函數gg的推導:我們欽點一部分作爲動態規劃的,剩下的自由排列,黃綠紅分別代表欽點的方案,剩下的一個點代表自由排列出現的,那麼一個藍色就對應了三種“至少”方案。

因此,可以得到上面的表達式。

4.代碼

下面是AC代碼

#include<bits/stdc++.h>
using namespace std;
int read(){
	char s;
	int x=0,f=1;
	s=getchar();
	while(s<'0'||s>'9'){
		if(s=='-')f=-1;
		s=getchar();
	}
	while(s>='0'&&s<='9'){
		x*=10;
		x+=s-'0';
		s=getchar();
	}
	return x*f;
}
const long long mod=1e9+9;
const int N=2020;
long long qpow(long long a,long long b){
	if(b==0)return 1;
	long long rec=qpow(a,b/2)%mod;
	if(b&1)return rec*rec%mod*a%mod;
	return rec*rec%mod;
}
int n,k;
int a[N],b[N];
int lst[N];//b中比ai小的數字個數 
long long inv[N];//階乘的逆元 
long long calc[N];//階乘 
long long dp[N][N];
long long g[N];//n對裏面有至少i對a>b的方案數 
void init(){
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++)b[i]=read();
	sort(a+1,a+1+n);
	sort(b+1,b+1+n);
	for(int i=1,id=0;i<=n;i++){
		while(b[id+1]<a[i]&&id<n)id++;
		lst[i]=id;
	}
	calc[0]=1;
	for(int i=1;i<=n;i++){
		calc[i]=(long long)calc[i-1]*i%mod;
	}
	inv[n]=qpow(calc[n],mod-2);
	for(int i=n-1;i>=0;i--){
		inv[i]=(long long)inv[i+1]*(i+1)%mod;
	}
}
int main(){
	n=read(),k=read();
	if((n+k)&1){
		printf("0");
		return 0;
	}
	k=(n+k)/2;
	init();
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		dp[i][0]=dp[i-1][0];
		for(int j=1;j<=i;j++){
			dp[i][j]=(dp[i-1][j]+dp[i-1][j-1]*max(0,lst[i]-j+1)%mod)%mod;
			dp[i][j]%=mod;
		} 
	}
	for(int i=0;i<=n;i++){
		g[i]=dp[n][i]*calc[n-i]%mod;
	}
	long long ans=0;
	long long id=1;
	for(int i=k;i<=n;i++){
		ans+=(long long)id*calc[i]%mod *inv[k]%mod*inv[i-k]%mod*g[i]%mod;
		ans=(ans%mod+mod)%mod;
		id=-id;
	}
	ans=(ans%mod+mod)%mod;
	printf("%lld\n",ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章