基础二项式反演 [已经没有什么好害怕的了]

link

大意:

给定两个数组a,b,要求对两个数组中的元素两两配对,使得恰好有ai>bi的组数恰好比ai<bi的组数多k对。问方案数

数据保证所有元素都不相同

思路:
设ai>bi的组数为x,则有x+x-k=n,推出x=(n+k)/2

所以如果n+k是个奇数的话就直接结束了

我们现在令k=(n+k)/2,那么问题就变成了找使得ai>bi的组数恰好为k的方案数

考虑二项式反演。

我们令fi表示a>b的组数恰好为i的方案数,显然答案就是fk

令gi表示a>b的组数至少为i的方案数

那么只要求出来g数组,我们直接套一个二项式反演就可以了

可以考虑通过dp来求出g数组

我们不妨先对两个数组排一下序,并设ri表示对于ai,b数组的前i个元素里面<ai的个数

设dpi,j表示前i个数字里面至少有j对a>b的配对

转移方程也很好想,如果a数组的第i个数字不选,方案数就是dpi-1,j,如果选上它,我们要为其找一个比它小的数字,那么方案数显然就是dpi-1,j-1*(ri-(j-1))

求出来dp之后,gi=(n-i)!*dp[n][i]

然后套一下二项式反演就可以了

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=5010;
const ll mod=1e9+9;
ll n,m;
ll p[N];
ll pp[N];
ll g[N];
ll num[N];
ll dp[N][N];
ll a[N],b[N];
ll ksm(ll x,ll y)
{
	ll ans=1;
	while(y)
	{
		if(y&1) ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ans;
}
ll inv(ll x)
{
	return ksm(x,mod-2);
}
void init()
{
	p[0]=1;
	for(ll i=1;i<=5000;++i) p[i]=p[i-1]*i%mod;
	pp[5000]=inv(p[5000]);
	for(ll i=5000-1;i>=0;--i)
	pp[i]=pp[i+1]*(i+1)%mod;
}
ll C(ll n,ll m){
	if(n<m) return 0;
	return p[n]*pp[m]%mod*pp[n-m]%mod;
}
void solve()
{
	cin>>n>>m;
	if((n+m)%2)
	{
		cout<<0<<endl;
		return;
	}
	m=(n+m)/2;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=n;++i) cin>>b[i];
	sort(a+1,a+1+n);
	sort(b+1,b+1+n);
	for(int i=1;i<=n;++i)
	{
		num[i]=lower_bound(b+1,b+1+n,a[i])-b-1;
	}
//	for(int i=1;i<=n;++i) cout<<num[i]<<" ";
//	cout<<endl;
    for(int i=0;i<=n;++i) dp[i][0]=1;
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=n;++j)
		{
			dp[i][j]=(dp[i-1][j]+dp[i-1][j-1]*(num[i]-j+1)%mod)%mod;
		}
	}
	for(int i=1;i<=n;++i) g[i]=dp[n][i]*p[n-i]%mod;
	ll ans=0;
	for(int i=m;i<=n;++i)
	{
		if((i-m)%2) ans=((ans-C(i,m)*g[i]%mod)%mod+mod)%mod;
		else ans=(ans+C(i,m)*g[i]%mod)%mod;
	}
	cout<<ans<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	init();
	//ll t;cin>>t;while(t--)
	solve();
	return 0; 
}

猜你喜欢

转载自blog.csdn.net/sophilex/article/details/129909574