【集训】组合计数

题单
不是自己写的,总结的

交换求和顺序:

  • $\sum_{i=1}^{n} \sigma_{0}(i)=\sum_{i=1}^{n} \sum_{d \mid i} 1=\sum_{d=1}^{n} \sum_{i=1}^{n}[d \mid i]=\sum_{d=1}^{n}\left\lfloor\frac{n}{d}\right\rfloor $
  • 给定序列 \(a\) ,求其所有区间的区间和:答案是 $\sum_{i=1}^{n} a_{i} \times i \times(n-i+1) $。

二项式定理:

\[(a + b)^{n} = \sum_{k=0}^{n} \binom{n}{k} a^{n-k} b^{k} \]

常见组合恒等式:

\[\binom{n}{m}\binom{m}{k}=\binom{n}{k}\binom{n-k}{m-k} \quad(n \geq m \geq k) \]

先从 \(n\) 个中选出 \(m\) 个,再从选出的 \(m\) 个里面选出 \(k\)\(\Longleftrightarrow\) 先从 \(n\) 个中选出最终的 \(k\) 个,再从剩下的 \(n-k\) 个中选出第一步选中但第二步末选中的 \(m-k\) 个。

\[i\binom{n}{i} = n\binom{n-1}{i-1} \]

利用\(\binom{n}{m}= \frac{n!}{m!(n-m)!}\) 展开即可得到证明。


\[\sum_{j=w}^{k} \binom{x}{j} = \sum_{i=1}{x}\binom{i-1}{w-1}\binom{n-i}{k-w} \]

相当于从 \(n\) 个元素中选出 \(k\) 个,但前 \(x\) 个元素中必须选出至少 \(w\) 个;这等价于选出的第 \(w\) 个元素的位置在 \(x\) 之前。


\[\sum_{i=0}^{m} \binom{n+i}{i} = \binom{n+m+1}{n+1} \]

考察 \(n+m+1\) 元集的 \(n+1\) 元子集的最后一个元素。


\[\sum_{i=0}^{n} \binom{n}{i}\binom{m}{k-i} = \binom{n+m}{k} \]

\(n+m\) 个元素中选出前 \(k\) 个,考察前 \(n\) 个元素中选出的数的个数。

常见模型:

\(n\) 种物品,第 \(i\) 种物品有 \(a_i\) 个,认为相同物品之间互不区分,则将这 \(\sum a_i\) 个物品排列的方案数为

\[\frac{(\sum_{i=1}^n a_i)!}{\prod a_i!} \]

\(n\) 个相同的物品无序地分为 \(m\) 组,每组都非空的方案数:相当于往 \(n-1\) 个空位里面插入 \(m-1\) 个板,因此方案数为 \(\binom{n-1}{m-1}\)

如果不要求每组非空,考虑直接给每组都加一个,方案数为 \(\binom{n+m-1}{m-1}\)


求方程 \(\sum_{i=1}^m x_i = n, x_i \in \mathbb{Z}, x_i \geq 0\) 的整数解个数:\(\binom{n+m-1}{m-1}\)

如果再给出序列 \(y_1...m\),要求 \(x_i \geq y_i\)
相当于 \(\sum_{i=1}^m (x_i - y_i) = n - \sum_{i=1}^m y_i\),故方案数为 \(\binom{n+m-\sum_{i=1}^m y_i-1}{m-1}\)

如果要求 \(x_i \leq y_i\) 呢?此时有两种做法:

  • DP:\(f(i, j)\) 表示前 \(i\) 个数和为 \(j\) 的方案数,转移用前缀和优化。\(O(nm)\)
  • 容斥:钦定 \(S \subseteq \{1, 2, \cdots, m\}\) 以内的限制不被满足,其余的不管,转化为要求 \(x_i > y_i\) 的情形。\(O(2^m \times \text{poly}(m))\)

求有多少长为 \(2n\) 的合法括号序列。答案记作 \(C_n\),这就是卡特兰数。

把左括号看成往右走,有括号看成往上走,那么这个就对应一条 \((0,0)\)\((n,n)\) 的路径,且不能穿过 \(y=x\)(但可以触碰)。

路径一共有 \(\binom{2n}{n}\) 种,考虑怎么算穿过的方案数,也就是触碰了 \(y=x+1\) 这条直线。

我们在最后触碰的位置 \((p, p+1)\)\((0,0) \rightarrow (p, p+1)\) 这一部分翻折,发现就唯一对应一条 \((-1,1) \rightarrow (n,n)\) 的路径,于是方案数就是 \(\binom{2n}{n-1}\)。于是我们有以下这样的关系

\[C_n = \binom{2n}{n} - \binom{2n}{n-1} \]

这就是卡特兰数。

另一方面,从括号序列的角度,我们还能给出另一种递推式:枚举第一个左括号匹配的右括号的位置为 \(2k\),有

\[C_n = \sum_{k=1}^{n} C_{k-1} C_{n-k} \]

其中 \(C_0 = 1\)

卡特兰数 \(C_n\) 常用套路

  • 对角线不相交的情况下,将一个 \(n\) 条边的凸多边形区域分成三角形区域的方法数,即三角剖分的方案数。
  • 一个大小为 \(n\) 的栈,依次入栈 \(1, 2, \cdots, n\),但可以在任意时机出栈,问有多少种出栈序。
  • \(n\) 个节点的无标号有根二叉树的个数。

P2671 [NOIP2015 普及组] 求和

现在考虑一组中有 \(m\) 个格子,则这一组对答案的贡献为

\[\sum\limits_{i=1}^{m} \sum\limits_{j=1}^{i-1} (i+j)\times (a_i+a_j) \]

若枚举 \(i,j\) 暴力计算,则复杂度为 \(O(n^2)\),会 TLE,所以需要进一步优化

现在我们把这个式子拆开(把括号打开)

\[\begin{equation} \begin{aligned} &\sum\limits_{i=1}^{m} \sum\limits_{j=1}^{i-1} (i+j) \times (a_i+a_j) \\ &= \sum\limits_{i=1}^{m} \sum\limits_{j=1}^{i-1} (i \times a_i+j\times a_i+i\times a_j+j\times a_j) \\ &= \sum\limits_{i=1}^{m} \sum\limits_{j=1}^{i-1} i\times a_i +\sum\limits_{i=1}^{m} \sum\limits_{j=1}^{i-1} j\times a_i +\sum\limits_{i=1}^{m} \sum\limits_{j=1}^{i-1} i\times a_j +\sum\limits_{i=1}^{m} \sum\limits_{j=1}^{i-1} j\times a_j \end{aligned} \end{equation} \]

第一个式子可以化简为

\[\sum\limits_{i=1}^{m} i\times a_i\times (i-1) \]

第二个式子可以化简为

\[\sum\limits_{i=1}^{m}a_i\times \dfrac{(i-1)\times i}{2} \]

第三个式子 \(\sum\limits_{i=1}^{m} \sum\limits_{j=1}^{i-1} i\times a_j\) 比较麻烦, 我们可以考虑每个 \(a_j\) 对答案的贡献

比如考虑 \(j=1\) 时的 \(a_1\),我们知道它的贡献应该是 \(\sum\limits_{i=2}^{m} i\times a_1\)

也就是说,当 \(i=2\sim m\) 时,会存在 \(i\times a_1\) 这一项

考虑 \(j=2\) 时的 \(a_2\) ,我们知道它的贡献应该是 \(\sum\limits_{i=3}^{m} i\times a_2\)

也就是说,当 \(i=3\sim m\) 时,会存在 \(i\times a_2\) 这一项

以此类推,第三个式子可以化简为

\[\sum\limits_{j=1}^{m} a_j\times \sum\limits_{i=j+1}^{m} i= \sum\limits_{j=1}^{m} a_j\times \dfrac{(j+1+m)\times (m-j)}{2} \]

对于第四个式子,我们可以考虑每个 \(j\times a_j\) 对答案的贡献

\(i=2\sim m\) 时,\(1\times a_1\) 这一项会出现一次

\(i=3\sim m\) 时,\(2\times a_2\) 这一项会出现一次

以此类推,第四个式子可以化简为

\[\sum_{i=1}^{m} \sum_{j=1}^{i-1} j \times a_j = \sum\limits_{j=1}^{m} j\times a_j\times \sum\limits_{i=j+1}^{m} 1= \sum\limits_{j=1}^{m} j\times a_j\times (m-j) \]

整理一下,可得:

\[\begin{equation} \begin{aligned} & \sum\limits_{i=1}^{m} \sum\limits_{j=1}^{i-1} (i+j)\times (a_i+a_j) \\ &= \sum\limits_{i=1}^{m} \left( i\times a_i\times (i-1)+a_i\times \dfrac{(i-1)\times i}{2}+a_i\times \dfrac{(i+1+m)\times (m-i)}{2}+i\times a_i\times (m-i) \right) \\ &= \sum\limits_{i=1}^{m} a_i\times \left( i^2 -i+ \dfrac{i^2-i}{2}+\dfrac{m\times i+m+m^2-i^2-i-m\times i}{2}+m\times i-i^2 \right) \\ &= \sum\limits_{i=1}^{m} a_i\times (i\times m-2i+ \dfrac{m^2+m}{2}) \\ &= \sum\limits_{i=1}^{m} i\times a_i\times (m-2) + \sum\limits_{i=1}^{m} a_i\times \dfrac{m^2+m}{2} \\ \end{aligned} \end{equation} \]

简单维护即可。

#include <bits/stdc++.h>
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
#define FOR(i,a,b) for(int i=(a);i<=(b);++i)
#define ROF(i,a,b) for(int i=(a);i>=(b);--i)
#define U unsigned
#define LL long long
using namespace std;
template<class T>
inline void read(T &a){ register U LL x=0,t=1; register char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') t=-1; ch=getchar();} while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } a=x*t;}
inline void print(LL x){if(x<0)putchar('-'),x=-x;if(x>9) print(x/10);putchar(x%10+'0');}

const int mod=10007;
const int maxn=1e5+5;
int n,m;
int a[maxn],b[maxn];
int s1[maxn][2],s2[maxn][2];
int ans;
void sovle(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) {
		cin>>b[i];
		s1[b[i]][i%2]++;
		s2[b[i]][i%2]=(s2[b[i]][i%2]+a[i])%mod; 
	}
	for(int i=1;i<=n;i++){
		int y=b[i];
		ans+=i*(s2[y][i%2]+a[i]*(s1[y][i%2]-2)%mod)%mod;
		ans%=mod;
	}
	cout<<ans<<endl;
	
}

signed main(){
    sovle();
 	return 0;
}

把同色,同奇偶性的放在一起考虑。设当前分离出来的这一组编号分别为 \(x_1, \cdots, x_k\),权值分别为 \(y_1, \cdots, y_k\),则贡献为

\[\begin{equation} \begin{aligned} & \sum_{i=1}^{k} \sum_{j=i+1}^{k} (x_i + x_j)(y_i + y_j) \\ &= \sum_{i=1}^{k} \sum_{j=i+1}^{k} x_i y_i + x_i y_j + x_j y_i + x_j y_j \\ &= \sum_{i=1}^{k} (k-i) x_i y_i + x_i \sum_{j=i+1}^{k} y_j + y_i \sum_{k=i+1}^{n} x_j + \sum_{k=i+1}^{n} x_j y_j \end{aligned} \end{equation} \]

预处理 \(x, y, xy\) 的后缀和即可。时间复杂度为 \(O(n)\)

AT_abc266_g [ABC266G] Yet Another RGB Sequence

首先我们考虑把 \(\texttt{rg}\) 算成字符 \(\texttt{\#}\)

题面即变为:求 \(R - k\)\(\texttt{r}\)\(G-k\)\(\texttt{g}\)\(B\)\(\texttt{b}\)\(k\)\(\texttt{\#}\) 组成不含 \(\texttt{rg}\) 的序列的方案数。

此时,我们先把 \(\texttt{g}\),\(\texttt{b}\)\(\texttt{\#}\) 排好,方案即为 \(C_{G-k +B+k}^{G-k} \times C_{B+k}^k\)

再考虑把 \(R-k\)\(\texttt{r}\) 加入,此时不能将 \(\texttt{r}\) 放在 \(\texttt{g}\) 前面,所以这一步有 \(C_{R-k+B+k}^{R-k}\) 种方案。

故共有 \(C_{G+B}^{G-k} \times C_{B+k}^k \times C_{R+B}^{R-k}\) 种方案。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int mod=998244353;
ll a,b,c,d,ans; 
ll poww(ll a,ll b){
	ll res=1;
	while(b){
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
ll C(int n,int m){
	if(m==0) return 1;
	ll res=1;
	for(int i=n;i>=n-m+1;i--)
		res=res*i%mod;
	for(int j=1;j<=m;j++)
		res=res*poww(j,mod-2)%mod;
	return res;
}
int main(){
	cin>>a>>b>>c>>d;
	a-=d,b-=d;
	ans=C(c+d+b,b)*C(d+c,d)%mod*C(c+d+a,a)%mod;
	cout<<ans<<endl;
	return 0;
} 

AT_abc240_g [ABC240G] Teleporting Takahashi

\(N<|X|+|Y|+|Z|\) 时无解。

否则,考虑先算出将 \(a\) 步分配到一个长为 \(b\) 的维度上,发现方案数是

\[\binom{a}{\frac{a+b}{2}} \]

不妨考虑两维的情况

\[\begin{aligned} F(N)&=\sum_{x=0}^N\binom{N}{x}\binom{x}{\frac{x+X}{2}}\binom{N-x}{\frac{N+Y-x}{2}}\\ &=\sum_{x=0}^N\dfrac{N!}{(\frac{x+X}{2})!(\frac{x-X}{2})!(\frac{N+Y-x}{2})!(\frac{N-Y-x}{2})!}\\ &=\dfrac{N!}{(\frac{N-X-Y}{2})!(\frac{N+X+Y}{2})!}\sum_{x=0}^N\dfrac{(\frac{N-X-Y}{2})! (\frac{N+X+Y}{2})!}{(\frac{x+X}{2})!(\frac{x-X}{2})!(\frac{N+Y-x}{2})!(\frac{N-Y-x}{2})!}\\ &=\dfrac{N!}{(\frac{N-X-Y}{2})!(\frac{N+X+Y}{2})!}\sum_{x=0}^N\binom{\frac{N-X-Y}{2}}{\frac{x-X}{2}}\binom{\frac{N+X+Y}{2}}{\frac{N+Y-x}{2}}\\ &=\binom{N}{\frac{N+X+Y}{2}}\binom{N}{\frac{N+X-Y}{2}} \end{aligned} \]

扩展到三维,答案就是

\[\sum_{z=0}^NF(N-z)\binom{N}{z}\binom{z}{\frac{z+Z}{2}} \]

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353,N=2e7+5;
ll fac[N],ifac[N],n,X,Y,Z;
int ksm(int x,int y,int p=mod){
	int ans=1;
	for(int i=y;i;i>>=1,x=1ll*x*x%p)if(i&1)ans=1ll*ans*x%p;
	return ans%p;
}
ll inv(int x,int p=mod){
	return ksm(x,p-2,p)%p;
}
void add(ll &x,ll v){
	x+=v;
	if(x>=mod) x-=mod;
}

ll C(int x,int y){
	if(x<y||y<0) return 0;
	return 1ll*fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}
ll F(int m){
	if((m+X+Y)%2!=0) return 0;
	return 1ll*C(m,(m+X+Y)/2)*C(m,(m+X-Y)/2)%mod;
}

signed main(){
	cin>>n>>X>>Y>>Z; 
	fac[0]=1;
	for(int i=1;i<=n*2;i++) fac[i]=1ll*fac[i-1]*i%mod;
	ifac[n*2]=inv(fac[n*2]);
	for(int i=n*2-1;i>=0;i--) ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
	ll ans=0;
	for(int z=0;z<=n;z++) 
		if((z+Z)%2==0) 
			add(ans,1ll*F(n-z)*C(n,z)%mod*C(z,(z+Z)/2)%mod);
			
	cout<<ans<<endl;
	return 0;
}

容斥原理 & 二项式反演

如果有 \(n\) 个约束 \(p_1, p_2, \cdots, p_n\),我们要求计数的对象符合所有约束;但符合约束时不易计算,反而是不符合约束时容易计算,此时便可以采用容斥的手法。

具体地,我们枚举一个约束的集合 \(S\),钦定 \(S\) 中的约束必须不被满足,\(S\) 之外的约束可以满足也可以不满足,再附上 \((-1)^{|S|}\) 的容斥系数,即可算出符合所有约束的方案数。

例:计算有多少个长为 \(n\) 的排列 \(a\) 满足:对每个 \(i = 1, 2, \cdots, n\),都有 \(a_i \neq i\)

如果各个约束之间本质相同,即钦定违反的集合 \(S\) 之后,方案数只和 \(S\) 的大小 \(|S|\) 有关,我们就得到了二项式反演

\[g_i = \sum_{j=i}^{n} \binom{j}{i} f_j \iff f_i = \sum_{j=i}^{n} (-1)^{j-i} \binom{j}{i} g_j \]

\(g_i\) 的实际含义是,钦定某 \(i\) 个元素被选中,其余不做要求的方案数。也就是说,选中恰好 \(j\) 个元素的方案会被算 \(\binom{j}{i}\) 次,即 \(g_i = \sum_{j=i}^{n} \binom{j}{i} f_j\)


常见容斥模型:

  • \(n\) 个球排成一列,每个球可以染成 \(m\) 种颜色中的一种,相邻球的颜色不能相同,但是每种颜色至少出现一次,问方案数。

  • 计算有多少个长为 \(m\) 的序列 \(x\) 满足:\(\sum_{i=1}^{m} x_i = n\),且对每个 \(i = 1, 2, \cdots, m\)\(0 \leq x_i \leq y_i\),其中 \(y_i\) 是给定的序列。

P10596 BZOJ2839 集合计数

给定 \(n, k\)。令 \(S_0 = \{1, 2, 3, \dots, n\},S_1 = \{S \mid S \subseteq S_0\}\),求有多少 \(S_1\) 的子集 \(S\) 满足 \(\left|\bigcap\limits_{S \in S_1} S\right| = k\)

仍然是钦定。设 \(f(k)\) 表示「至少」\(k\) 个元素是集合的交集,即钦定 \(k\) 个元素作为集合的交集,剩余不做限制的方案数,会有重复。有:

\[f(k) = \dbinom nk \left(2^{2^{n-k}}-1\right) \]

\(g(k)\) 表示恰好 \(k\) 个元素是集合的交集的方案数,有:

\[f(k) = \sum_{i=k}^n \dbinom ik g(i) \]

二项式反演得:

\[g(k) = \sum_{i=k}^n(-1)^{i-k} \dbinom ik f(i) = \sum_{i=k}^n(-1)^{i-k} \dbinom ik \dbinom ni \left(2^{2^{n-i}}-1\right) \]

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 7,mod = 1000000007;
int ksm(int a, int b, int md) {
    int res = 1;
    while(b) {
        if(b & 1) res = res * a % md;
        a = a * a % md;
        b >>= 1;
    }
    return res % md;
}
int fac[N], infac[N];
int getc(int a, int b) {
    return fac[a] % mod * infac[b] % mod * infac[a - b] % mod;
}
signed main() {
    int n, k;
    cin >> n >> k;
    fac[0] = infac[0] = 1;
    for(int i = 1; i < N; i ++) {
        fac[i] = fac[i - 1] * i % mod;
        infac[i] = ksm(fac[i], mod - 2, mod) % mod;
    }   
    int ans = 0,p = getc(n, k);
    n -= k;
    for(int i = n; i >= 0; i --) {
        if(i % 2 == 0)
            ans = (ans + getc(n, i) % mod * (ksm(2ll, ksm(2ll, n - i, mod - 1), mod) - 1) % mod) % mod;
        else 
            ans = (ans - getc(n, i) % mod * (ksm(2ll, ksm(2ll, n - i, mod - 1), mod) - 1) % mod + mod) % mod;
        ans %= mod;
    }
    cout << (ans * p + mod) % mod << endl;
}

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

给出 \(n\) 个数 \(a_i\) ,以及 \(n\) 个数 \(b_i\) ,要求两两配对使得 \(a>b\) 的对数减去 \(a<b\) 的对数等于\(k\)

我们假设 \(a>b\) 对数为 \(x\) ,可以求得 \(x=\frac{n+k}{2}\) 。 我们令 \(f_{i,j}\) 表示前 \(i\)\(a\) 中,选了 \(j\) 组满足 \(a>b\) 的方案数。

容易得到 \(\text{dp}\) 方程 $$f_{i,j}=f_{i-1,j}+(l_i-j+1)\times f_{i-1,j-1}$$ 其中 \(l_i\) 表示 \(b\)\(<a_i\) 的个数。

我们记 \(g_i=f_{n,i}\times (n-i)!\) 即满足 \(a>b\) 的组数 \(\geq i\) 的方案数,再令 \(f_i\) 表示恰好满足 \(a>b\) 的组数 \(= i\) 的方案数。

容易发现对于 \(i>j\) \(f_i\) 恰好在 \(g_j\) 中算了 \({i\choose j}\) 次。

那么存在 \(g(k)=\sum_{i=k}^n{i\choose k}f(i)\) 由二项式反演得 \(f(k)=\sum_{i=k}^n(-1)^{i-k}{i\choose k}g(i)\) 直接求解即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 2000+5, P = 1e9+9;
long long n, k, a[N], b[N], l[N], f[N][N], fac[N], ifac[N], g[N];
long long C(int n, int m) {
	return 1ll*fac[n]*ifac[m]%P*ifac[n-m]%P; 
}
long long power(long long x,long long y){ 
	long long res = 1;
	while(y){
		if(y & 1)
		    res = res * x % P;
		x = x * x % P;
		y >>= 1;
	}
	return res;
}
int main(){
	cin>>n>>k;
	if ((n+k)&1) return puts("0"),0;   
	k = (n+k)/2;
	ifac[0] = ifac[1] = fac[0] = fac[1] = 1;
	for(int i = 1;i <= n;i++)
	    fac[i] = fac[i - 1] * i % P; 
	ifac[n] = power(fac[n],P - 2);
	for(int i = n;i >= 1;i--)
	    ifac[i - 1] = ifac[i] * i % P;
	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); 
	sort(b+1, b+n+1);
	int ll = 0;
	for (int i = 1; i <= n; i++) {
		while (ll < n && b[ll+1] < a[i]) ++ll;
		l[i] = ll;
	}
	
	f[0][0] = 1;
	for (int i = 1; i <= n; i++) {
		f[i][0] = f[i-1][0];
		for (int j = 1; j <= i; j++)
			f[i][j] = (1ll*f[i-1][j]+1ll*f[i-1][j-1]*max(0ll, l[i]-j+1)%P)%P;
	}
	for (int i = 0; i <= n; i++) g[i] = 1ll*f[n][i]*fac[n-i]%P;
	int ans = 0;
	for (int i = k; i <= n; i++)
		if ((i-k)&1) (ans -= 1ll*C(i, k)*g[i]%P) %= P;
		else (ans += 1ll*C(i, k)*g[i]%P) %= P;
	cout<<(ans+P)%P<<endl;
}
posted @ 2025-01-21 16:37  Star_F  阅读(92)  评论(0)    收藏  举报