P10596 BZOJ2839 集合计数 题解

题目传送门

前置知识

二项式反演 | 乘法原理 | 扩展欧拉定理 | 容斥原理

解法

考虑二项式反演。

\(f_{m}\) 表示交集中恰好有 \(m\) 个元素的方案数,\(g_{m}\) 表示交集中至少有 \(m\) 个元素的方案数,即 \(g_{m}=\sum\limits_{i=m}^{n}\dbinom{i}{m}f_{i}\),那么有 \(f_{m}=\sum\limits_{i=m}^{n}(-1)^{i-m}\dbinom{i}{m}g_{i}\)

现在问题转化为怎么求 \(g_{m}\)。先钦定选出的 \(m\) 个元素,此时方案数为 \(\dbinom{n}{m}\);接着剩下的 \(n-m\) 个元素一共可以组成 \(2^{n-m}\) 个集合(包括空集),又因为这些集合中至少要选 \(1\) 个,故状态转移方程为 \(g_{m}=\dbinom{n}{m}(2^{2^{n-m}}-1)\)

\(\begin{aligned} 2^{2^{n-m+1}}=2^{2^{n-m} \times 2}=2^{2^{n-m}+2^{n-m}}=(2^{2^{n-m}})^{2} \end{aligned}\) 倒序处理即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define ull unsigned long long
#define sort stable_sort 
#define endl '\n'
const ll p=1000000007;
ll g[1000010],jc[1000010],inv[1000010],jc_inv[1000010];
ll C(ll n,ll m,ll p)
{
	return (n>=m&&n>=0&&m>=0)?((jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p):0;
}
int main()
{
	ll n,k,i,mi=2,f=0;
	cin>>n>>k;
	inv[1]=1;
	jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
	for(i=2;i<=n;i++)
	{
		inv[i]=(p-p/i)*inv[p%i]%p;
		jc[i]=jc[i-1]*i%p;
		jc_inv[i]=jc_inv[i-1]*inv[i]%p;
	}	
	for(i=n;i>=k;i--)
	{
		g[i]=C(n,i,p)*mi%p;
		mi=mi*mi%p;
	}
	for(i=k;i<=n;i++)
	{
		f=(f+((i-k)%2==0?1:-1)*C(i,k,p)*g[i]%p+p)%p;
	}
	cout<<f<<endl;
	return 0;
}
posted @ 2024-06-24 22:35  hzoi_Shadow  阅读(13)  评论(0编辑  收藏  举报
扩大
缩小