洛谷P4859已经没有什么好害怕的了-二项式反演

题目

题目链接

题目大意:有两组长度为n的数ab,组间数字互不相同。两组数字之间两两配对,不重不漏,给定一个数字k,求满足a中数字>b 比 b中数字>a 的组数多k的分组方案数

题目分析

1.问题转化

因为组间数字两两不同,所以其实就是告诉你了有多少组a比b大。

令新k= n + k 2 \frac{n+k}{2} 2n+k,那么如果n+k是一个奇数答案为0.

现在就变成了询问 a > b a>b a>b的数目为k的方案数

2.动态规划

首先对ab数组排序

直接求正好为k的不太好求,因为我们考虑从前往后给a配对。

1.如果当前数字匹配了一个比它小的,那很好办。

2.如果比他大,还要考虑被选的这个b组数对后面的影响。

第一种情况很好算,令 d p i , j dp_{i,j} dpi,j表示前i个数字选了j个数字是比b组数字大的

那么转移方程: d p i , j = d p i − 1 , j + ( l a s t i − ( j − 1 ) ) ∗ d p i − 1 , j − 1 dp_{i,j}=dp_{i-1,j}+(last_{i}-(j-1))*dp_{i-1,j-1} dpi,j=dpi1,j+(lasti(j1))dpi1,j1

其中 l a s t i last_{i} lasti表示b数组里面有这么多个数字比 a i a_i ai

因为我们不考虑第二种,也就是说除去前面那 j − 1 j-1 j1组a>b的b被匹配了,其余的 l a s t i − ( j − 1 ) last_{i}-(j-1) lasti(j1)组b>a的我们没有考虑,默认是都可以选。

3.二项式反演

那么我们在n组数字里面确定了j个是a>b,剩下的就随便排列,所以是 d p n , i ∗ ( n − i ) ! dp_{n,i}*(n-i)! dpn,i(ni)!

这个代表a>b的组数至少是i个的情况。

我们令g(i)表示恰好有i组数字是a>b

g ( k ) = d p n , k ∗ ( n − k ) ! g(k)=dp_{n,k}*(n-k)! g(k)=dpn,k(nk)!

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

根据二项式反演: g ( i ) = ∑ k = i n ( − 1 ) k − i C k i f ( k ) g(i)=\sum_{k=i}^{n}{(-1)^{k-i}C_{k}^{i}f(k)} g(i)=k=in(1)kiCkif(k)

二项式反演的基本介绍和证明见:二项式反演

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

更新:

更新前没讲明白这个f的表达式怎么来的,很多博文也没说清楚,非常抱歉

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

这个组合数很多人可能不理解。

在这里插入图片描述

画个图辅助理解:我们看蓝色点,表示恰好3个。那么绿、黄、红都代表至少两个。

那么在f函数里面,恰好3个的一种方案:蓝色点,在至少两个里面出现了三次。

因为我们回到“至少”函数 g g g的推导:我们钦点一部分作为动态规划的,剩下的自由排列,黄绿红分别代表钦点的方案,剩下的一个点代表自由排列出现的,那么一个蓝色就对应了三种“至少”方案。

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

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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值