二项式反演—简介

讲解

形式1:

形式2:

证明方法可以参考这篇博客:二项式反演及其应用 - GXZlegend - 博客园

证明思路大概就是把g(n)=....代入到左边的式子去,然后进行sigma前后交换、组合数交变、适时提取共有项、出现"sigma+0"消掉、最后根据定义得到了答案. 大概是这样一个证明思路f(n)=....g(i)=....f(i)=.......#=f(n)。

二项式反演其实就是一种容斥。不过是更加模板化的容斥。

在遇到恰好问题不好解决时,就可以考虑解决至少、至多的问题,这个问题解决就是f(n)的值。一般是会比较好求的,用点数学方法或者DP就可以解决。然后,用二项式反演,求出g(n)。这就是恰好的方案数了。

一般都是这么一个思路。

例题依旧可以参考这篇博客:二项式反演及其应用 - GXZlegend - 博客园。

解释一下“钦定k个”是个啥意思。就是先选定k个让它们一定成立,然后剩下n-k个就随意它们组合,那可以预见,最终结果可能会出现,成立的不只是刚开始选的那k个,还在随机组合过程中多成立了一些。所以才有“至少”、“至多”、“恰好”这一说。这便是钦定,用理科生的概括来说,就是:选定k一定成立,剩下随机。

 洛谷迷给出洛谷的提交代码方法,嘻嘻

例题1

P5505 [JSOI2011]分特产

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD=1e9+7;
const int N=1e3+10;

ll C[2*N][2*N];
int pre_C(int n)
{
	C[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		C[i][0]=1;
		for(int j=1;j<=i;j++)
		{
			ll tmp=C[i-1][j-1]+C[i-1][j];
			C[i][j]=tmp>=MOD?tmp-MOD:tmp;
		}
	}
}

int a[N];
int main()
{
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++) scanf("%d",&a[i]);
	pre_C(2000);
	ll ans=0;
	for(int i=0;i<=n;i++)
	{
		ll tmp=C[n][i];
		for(int j=1;j<=m;j++) tmp=tmp*C[a[j]+n-i-1][a[j]]%MOD;
		ans=(ans+(i&1?-tmp:tmp)+MOD)%MOD;
	}
	printf("%lld",ans);
	return 0;
}

例题2

P4859 已经没有什么好害怕的了

这题难倒我的是DP。。。

显然m=(n+k)/2为需求A>B的个数;A,B需要排序。

dp[i][j]表示前i个中,钦定j组的方案数。

cnt[i]表示for(int j=1;j<=n;j++) cnt[i]+=a[i]>b[j];

dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*(cnt[i]-(p-1))

把dp[i][0]=1, 初始化就ok了. 

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD=1e9+9;
const int N=2e3+10;

ll C[N][N];
int pre_C(int n)
{
	C[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		C[i][0]=1;
		for(int j=1;j<=i;j++)
		{
			ll tmp=C[i-1][j-1]+C[i-1][j];
			C[i][j]=tmp>=MOD?tmp-MOD:tmp;
		}
	}
}

int a[N],b[N];
ll dp[N][N];int cnt[N],pre[N];
ll fact[N];
int main()
{
	int n,m,k;
	scanf("%d %d",&n,&k);m=(n+k)/2;
	if((n+k)&1)
	{
		puts("0");
		return 0;
	}
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
	sort(a+1,a+n+1);
	sort(b+1,b+n+1);
	pre_C(n);
	
	int p=0;
	for(int i = 0 ; i <= n ; i ++ ) dp[i][0] = 1;
    for(int i = 1 ; i <= n ; i ++ )
    {
        while(p < n && b[p + 1] < a[i]) p ++ ;
        for(int j = 1 ; j <= n ; j ++ )
            dp[i][j] = (dp[i - 1][j] + dp[i - 1][j - 1] * (p - j + 1)) % MOD;
    }
	
	fact[1]=1;
	for(int i=2;i<=n-m;i++) fact[i]=fact[i-1]*i%MOD;
	
	ll ans=0;
	for(int i=m;i<=n;i++)
	{
		ll tmp=C[i][m]*dp[n][i]%MOD*fact[n-i]%MOD;
		if((i-m)&1) ans=(ans-tmp+MOD)%MOD;
		else ans=(ans+tmp)%MOD;
	}
	printf("%lld",ans);
	return 0;
}

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值