Caiwen的博客

组合数学(新)

2025-02-21 12:57:56

容斥

二项式反演

我们令 f(i)f(i) 表示至少 ii 个元素满足某个条件,其他元素随便的方案数。g(i)g(i) 表示恰好 ii 个元素满足某个条件的方案数。一般 f(i)f(i) 很好求出,但我们想求 g(i)g(i),就可以使用二项式反演:

形式一:

f(n)=i=0nCni g(i)g(n)=i=0n(1)ni Cni f(i)f(n)=\sum_{i=0}^n C_n^i \space g(i)\Longleftrightarrow g(n)=\sum_{i=0}^n(-1)^{n-i}\space C_n^i\space f(i)

形式二:

f(n)=i=nmCin g(i)g(n)=i=nm(1)in Cin f(i)f(n)=\sum_{i=n}^m C_i^n \space g(i)\Longleftrightarrow g(n)=\sum_{i=n}^m(-1)^{i-n}\space C_i^n\space f(i)

题目

P4071 [SDOI2016] 排列计数

https://www.luogu.com.cn/problem/P4071

题目描述

求有多少种 11nn 的排列 aa,满足序列恰好有 mm 个位置 ii,使得 ai=ia_i = i

答案对 109+710^9 + 7 取模。多组测试数据

数据范围

对于全部的测试点,保证 1T5×1051 \leq T \leq 5 \times 10^51n1061 \leq n \leq 10^60m1060 \leq m \leq 10^6

笔记

f(x)f(x) 表示为有 xx 个位置满足 ai=ia_i=i,剩下的位置随便放,那么就有 f(x)=Cnx (nx)!=n!x!f(x)=C_n^x\space (n-x)!=\frac{n!}{x!}

但是 f(x)f(x) 表示的是至少有 xx 个位置满足要求,可能满足要求的位置不止 xx 个,我们要求的是恰好 xx 个位置,那么令恰好 xx 个位置满足要求的方案数为 g(x)g(x),有

f(x)=i=xnCix g(i)f(x)=\sum_{i=x}^n C_i^x \space g(i)

反演:

g(x)=i=xn(1)ix Cix f(i)=i=xn(1)ix i!(ix)!x! n!i!=n!x!i=0nx(1)ii!\begin{align} g(x)&=\sum_{i=x}^n(-1)^{i-x}\space C_i^x\space f(i) \\ &=\sum_{i=x}^n(-1)^{i-x}\space \frac{i!}{(i-x)!x!}\space \frac{n!}{i!} \\ &=\frac{n!}{x!}\sum_{i=0}^{n-x}\frac{(-1)^i}{i!} \end{align}

后面那个求和式可以提前预处理出来,于是我们就可以 O(1)O(1) 回答每个询问

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 #define debug(x) cout<<#x<<"="<<x<<endl using namespace std; const int inf=0x3f3f3f3f3f3f3f3f; const int mod=1e9+7; #define _ 1000006 typedef pair<int,int> pii; int fac[_],inv[_],pre[_]; inline int qpow(int a,int p,int res=1){for(;p;a=a*a%mod,p>>=1) if(p&1) res=res*a%mod;return res;} inline void init(){ fac[0]=1; for(int i=1;i<=1000000;i++) fac[i]=fac[i-1]*i%mod; inv[1000000]=qpow(fac[1000000],mod-2); for(int i=1000000-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod; pre[0]=1; for(int i=1;i<=1000000;i++) pre[i]=(pre[i-1]+(i%2==0?1:-1)*inv[i]+mod)%mod; } inline void subtask(){ int n,m;cin>>n>>m; cout<<fac[n]*inv[m]%mod*pre[n-m]%mod<<endl; } signed main(){ init(); ios::sync_with_stdio(false); int t;cin>>t; while(t--) subtask(); return 0; }
P10596 BZOJ2839 集合计数

https://www.luogu.com.cn/problem/P10596

题目描述

一个有 NN 个元素的集合有 2N2^N 个不同子集(包含空集),现在要在这 2N2^N 个集合中取出若干集合(至少一个),使得它们的交集的元素个数为 KK,求取法的方案数,答案模 109+710^9+7

数据范围

对于 100%100\% 的数据,1N10000001\leq N\leq 10000000KN0\leq K\leq N

笔记

f(x)f(x) 表示交集元素数量至少为 xx 的方案数,那么有 f(x)=Cnx (22nx1)f(x)=C_n^x\space(2^{2^{n-x}}-1) (先选出这 xx 个数,然后剩下的 nxn-x 个数构成 2nx2^{n-x} 个集合,这些集合随便选,但不能一个都不选)

g(x)g(x) 表示交集元素数量恰好为 xx 的方案数,则有 f(x)=i=xnCix g(i)f(x)=\sum_{i=x}^n C_i^x\space g(i)

反演,有 g(x)=i=xnCix (1)ixf(i)g(x)=\sum_{i=x}^nC_i^x\space (-1)^{i-x}f(i)

直接求即可,其中求 f(x)f(x) 的时候需要用到扩展欧拉定理

已经没有什么好害怕的了

https://vjudge.net/problem/黑暗爆炸-3622

题目描述

给出长度为 nn 的数组 aabb,这 2n2n 个数字互不相同。现在让 aa 中的数字和 bb 中的数字两两配对,使得 aa 中大于 bb 中的对数比 bb 中大于 aa 中的对数恰好多 kk 个,求方案数

数据范围

1n20001\le n \le 2000

笔记

显然可以转化为要求配对出恰好 n+k2\frac{n+k}{2}aabb 大的组,如果 nnkk 不同奇偶则无解

首先对 aa 中的数字进行排序,然后求得一个数组 cccic_i 表示 bb 中有多少个数字可以小于 aia_i。我们发现,如果 i<ji<j,那么比 aia_i 小的数字一定比 aja_j 小,或者说如果我们给 aia_i 配对某个数之后,再考虑可供与 aja_j 配对的数字个数时,可以用 cjc_j 减去当前已知匹配出来的组数。

于是就有 dp[i][j]dp[i][j] 表示前 ii 个数字匹配出来了 jj 组,dp[i][j]=dp[i1][j]+dp[i1][j1](cjj+1)dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*(c_j-j+1)

再深入考虑一下 dp[i][j]dp[i][j] 的含义,我们是钦定了 jj 组,对于其他位置,我们还没有考虑怎么分配

f(x)=(nx)!×dp[n][x]f(x)=(n-x)!\times dp[n][x],也就是我们把其余 nxn-x 个位置随便配对,这些位置既可能是 aabb 大,也可能是 bbaa 大,于是 f(x)f(x) 就变成了至少有 xxaabb 大的方案数。但我们要求恰好 xx 组的方案数,于是走个二项式反演就齐活了

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 #define debug(x) cout<<#x<<"="<<x<<endl using namespace std; const int inf=0x3f3f3f3f3f3f3f3f; const int mod=1e9+9; #define _ 2003 typedef pair<int,int> pii; int a[_],b[_],c[_],dp[_][_],fac[_],inv[_],n,k; inline int qpow(int a,int p,int res=1){for(;p;p>>=1,a=a*a%mod) if(p&1) res=res*a%mod;return res;} inline int con(int n,int m){return fac[n]*inv[m]%mod*inv[n-m]%mod;}; inline int f(int x){return fac[n-x]*dp[n][x]%mod;}; inline void subtask(){ cin>>n>>k; if((n+k)%2) return cout<<0,void(); fac[0]=1;for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod; inv[n]=qpow(fac[n],mod-2);for(int i=n-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) cin>>b[i]; sort(a+1,a+n+1); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(b[j]<a[i]) c[i]++; dp[0][0]=1; for(int i=1;i<=n;i++){ dp[i][0]=1; for(int j=1;j<=i;j++){ dp[i][j]=(dp[i-1][j]+dp[i-1][j-1]*max(c[i]-j+1,(int)0)%mod)%mod; } } int ans=0,x=(n+k)/2; for(int i=x;i<=n;i++) ans+=con(i,x)*((i-x)%2==1?-1:1)*f(i)%mod,ans%=mod; cout<<(ans+mod)%mod; } signed main(){ ios::sync_with_stdio(false); int t=1;//cin>>t; while(t--) subtask(); return 0; }
最后更新于:2025-04-03 14:03:11

Caiwen
本文作者
一只蒟蒻,爱好编程和算法

推荐文章