组合基础

OI 中的组合,基本指组合计数。组合极值一般是贪心题或者 dp 题。

【组合数】

组合数 Cnm=(mn)

注意:求逆元前,请一定判断清楚,是否可能不存在逆元!!!

  1. Cnm=Cn1m+Cn1m1

    c[n][m] = c[n - 1][m] + c[n - 1][m - 1];

    这个方法主要问题在于空间。

    优点:可以把 c[1 ~ n][1 ~ m] 全部求出来,好写。

    缺点:空间大。

    复杂度:O(nm)

    适用情况:n,m 都小。

  2. Cnm=n×(n1)××(nm+1)m×(m1)××1

    问题:我们不可能把分子乘完再除以分母,会爆的。

    方法一:Cnm=i=1mni+1i。 但是这个方法可能会爆。

    方法二:求出 1m 的逆元。然后把除换成乘。一路直接乘过去,边乘边模。

    复杂度:算一次 O(m)

    适用情况:m 小。

  3. Cnm=n!(nm)!m!=n!(m!)1[(nm)!]1

    只要能求出阶乘的逆元,就可以 O(1) 计算。

    我们考虑 (n!)1[(n+1)!]1 互相转化。

    1(n+1)!×[(n+1)!]1n!×(n+1)[(n+1)!]1

    所以 (n!)1[(n+1)!]1(n+1)

    我们先欧拉函数暴力计算 n! 的逆元,然后从大往小推出所有阶乘逆元。

    注意:我们这里是可以从 n+1n,而不是 nn+1

    code

    优点:O(n) 预处理之后,每次询问 O(1)

    复杂度:O(n)

    适用场景:O(n) 可行,需要多次查询。

  4. 卢卡斯定理(Lucas)

    二项式定理:(a+b)n=i=0nCnianibi

    引理:对 k:1(p1)p 为质数,pCpk

    引理证明:Cpk=p!k!(pk)!,分子有一个 p,分母没有 p

    卢卡斯定理:CnmCn/pm/p×Cn%pm%p(modp)

    (若 Cnmm>n,则 Cnm=0

    证明:

    n=ap+b,m=kp+t(0b,t<p)

    (1+x)n=Cn0x0+Cn1x1++Cnmxm++Cnnxn.

    (1+x)n=(1+x)ap+b=(1+x)pa×(1+x)b(1).

    (1+x)p=Cp0x0+Cp1x1++CppxpCp0+Cppxp

    所以 (1)(1+xp)a×(1+x)b[Ca0(xp)0+Ca1(xp)1++Cak(xp)k+][+Cbtxt+].

    发现 xkp+t 次项,一定是从左边括号选出 kp 次项,右边括号选出 t 次项,然后相乘得出的。

    所以 xkp+t 次项的系数一定是 Cak×Cbt

    而从二项式定理的展开中知道,xm=kp+t 次项的系数应该是 Cnm

    所以 Cnm=Cak×Cbt=Cn/pm/p×Cn%pm%p

    证毕。

    复杂度:O(plogpn)

    适用场景:n,m 非常大,O(p) 可行。或者 n,mp,不能直接求逆元(不能保证互质),用卢卡斯定理转化。

数三角形

三角形个数 = 任取三个点的方法数 - 三点共线个数。

总数:C(n+1)(m+1)3

行的三点共线:(n+1)C(m+1)3

列的三点共线:(m+1)C(n+1)3

斜的:

考虑枚举所有点,对于一个点,枚举所有斜率,看这条线上有多少个点,然后用组合数求出来。

但是发现一个斜率可能有很多个点所得出的答案一样。

于是我们改一下顺序:先枚举所有斜率,然后对于所有可以放上这个斜率的点,求出这个斜率上所有点,然后再减去。

斜率可以看做一个长方形的对角线。于是我们枚举这个长方形的长 i 和宽 j。那么可以作为长方形左上角的点就有 (n+1i)(m+1j) 个。

在每一个长 ij 的长方形对角线上,一共有 gcd(i,j)+1 个格点。(仪仗队)因此,取左上角、右下角和对角线中间 gcd(i,j)1 个点中任意一个点都能组成一组三点共线,并且每一组之间不会重复。

另外,还要注意,长方形有两条对角线,所以每次减的时候要减两份。

code

生成字符串

其实就是卡特兰数。

考虑把问题对应成一个 m×n 的方格表(n 是横着的)。最初我们在左下角,现在要去到右上角,每次可以向右或者向上移动一格。

把向右移动一格看做是在前面放一个 1,向上移动一格看做是放一个 0。那么移动到右上角就相当于我们取完了所有 0 和 1。每条移动的路径都和一个字符串一一对应。

条件 “任何前缀中 1 的个数必须不少于 0”,就相当于从左下角画一条 45° 向右上角走的斜线,不能越过这条斜线。

把这条斜线整体上移一个,条件就变成了不能碰到这条斜线。

我们还是考虑总数减去不符合要求的数。

总数:C(n+m)m,即从 n+m 步中选 m 个位置向上走。

不符合方案(碰到斜线):

把这个方格表的左边加一列,并把斜线往左下角延伸一个。此时的斜线就连到了新方格表的左下角。

考虑所有不符合的方案,一定存在第一个点,碰到了斜线。
我们把这个点之前进行的所有步,都关于这条斜线做对称。

这样我们就得到了一条新路径:从新表左下角的上面一格出发,走到右上角。

我们发现,每一条这种路径,都与一条不符合要求的路径一一对应。并且,这种路径一共走了 n+m 步,其中有 m1 步向上。

所以,这种路径的数量是 C(n+m)(m1)

因此,答案就是 C(n+m)mC(n+m)(m1)

code

可重组合

插板法。

k 种里面选 x 个。考虑第 i 种选了 xi 个。就是解 x1+x2++xk=x非负整数解。

Cx+k1k1,也可以说是 Cx+k1x

建设城市

注意:n,n+1 之间没有要求。

两种情况:x,y 一左一右,或者 x,y 同侧。

  1. 一左一右。

    假设高度 k。则 1x1 的楼高度取值 1k

    我们发现,只要取出了 x1 个高度,其排列方式已经固定了。

    所以,这就是一个 “可重组合”。从 k 种里面选 x1 个,答案 Ck+x2x1

    之后另外三个部分都差不多,求完四块之后全部相乘,就是 x,y 高度为 k 的方案数。

    复杂度 O(n+m)。因为 O(n) 预处理组合数(第三种),O(m) 枚举 x,y 的高度。

  2. 同侧。

    显然,同左和同右是对称的。

    这种其实就是只有三个部分——x,y 中间夹着的一定高度都是 k

【容斥原理】

有三个集合 A,B,C。现在想要知道 |ABC|

|ABC|=|A|+|B|+|C||AB||BC||AC|+|ABC|

例如,1n235 的倍数有几个,就是 n/2+n/3+n/5n/6n/15n/10+n/30。(下取整)

容斥原理:

|A1A2An|=|Ai|ij|AiAj|+|AiAjAk|.

证明方法考虑归纳法。


容斥原理的题目一般长这样:

符合条件 t1,t2,,tn 至少一个的方法数。(1)

t1,t2,,tn 的不符合的方法数。(2)

符合 ti 的情况,记为 Ai。全集记为 S

(1):答案为 A1A2An

(2):答案为 CS(A1A2An)

而全集一般都很好求。所以情况(1)和情况(2)可以视为等价的。

不妨称 ti 为 “基本条件”,Ai 为 “基本集合”。


容斥原理的代码:

写一个子集枚举,传参数记录选了多少个集合交起来。然后根据参数判断当前的答案是正是负。


无平方数

pi 为第 i 个质数,250 内有 k 个质数(k 大概是 3×107)。Ai 为符合 “是 pi2 的倍数” 条件的集合。

A1A2Ak 为所有 “非 squarefree” 的数。

|Ai|=[250pi2]|AiAj|=[250(pipj)2]|AiAjAt|=[250(pipjpt)2]……

这个并集是不好算的,但是交集很简单。

我们可以搜索——搜索出所有若干个质数相乘的所有次数为 1 的数,并同时记录每个数是由多少个质数乘起来的。(当然,质数乘起来必须 225,否则平方就炸掉了。)

code

硬币购物

如果只有一次询问,可以直接多重背包加单调队列优化,但是有多次询问。

ti 为 “第 i 种硬币使用超量”,Ai 为对应集合。

t1t2t3t4 为 “至少有一种硬币超量”,是所有不符合条件的情况。

dp[x] 为凑 x 面值,每种硬币随便用的方法数。(完全背包)

|AiAj|=dp[s(di+1)ci(dj+1)cj],因为先把 i,j 两种取到保证超量。

因为硬币种数很少,所以每次询问求一次容斥都没压力。

一个东西选超量:先把它取完,再多取一个,最后随便选。

分特产

如果不要求每人至少拿一个,我们可以先把第一个分给所有人,然后分第二个…… 分每一个之间相互独立,分步计数。

如果要求每人至少一个,当前每个人是否拿到了就会影响后面的特产分发,不能分步计数。

但是,我们可以考虑反面至少有一个人没有特产。

ti:第 i 个人没拿到特产。

想要的:A1A2An 的补集。(所有同学都分到了)

|Ai|= 所有特产分给另外 n1 个人的方法数。

|AiAj|= 所有特产分给另外 n2 人方法数 =Cai+(n21)ai。(隔板法)

另外注意到,无论是哪两个同学没分到,答案都相等。所以我们不需要真的枚举所有子集。

一个人没分到:把所有东西都分给别人。

染色问题

分步考虑三个条件。

  1. ti 为第 i 种颜色不用。想要的是所有集合并起来的补集:所有颜色都用过。

    |AiAj|= 剩下 C2 种颜色随便染的方法数。

    复杂度贡献乘 C,无论是选第 1,2 种颜色,还是选第 3,4 种颜色,答案都是一样的。

  2. k 种颜色可选。ti 为第 i 行无色。所有集合的并集的补集:每一行都有色。

    |AiAj|=n2m 列染色,不要求每行有色。

  3. xyk 种颜色,只需每列有色。不考虑另外两个条件。

    [(k+1)x1] 是某一列有色的情况数:一列 x 个,每个有 k+1 种染色方式(包括不染),去掉每一个都不染的情况。

    一共 y 列,[(k+1)x1]y 种方法。

用两次容斥,去掉两个条件。

//用x种颜色染色的方法,仍要求行列非全无色,但x种颜色不必都用过
long long cnt(long long x) {
	long long ans = 0;
	// 其中i行可以有颜色,即某n-i行无色
	for (long long i = n, t = 1; i >= 1; i--, t *= -1)
		ans = (ans + fpow((fpow(x + 1, i) - 1)/*一列全部方案减去一种全无色*/, m)/*所有列非全无色*/ * t * cmb(n, i) % p + p) % p;
	//X种颜色不必都用过(但每行每列必须有色)=X种颜色涂n行(颜色不必都用每行不必有色但每列必须有色)-X种涂n-1行(颜色不必都用每行不必有色但每列必须有色) + X种颜色涂n-2行(颜色不必都用每行不必有色但每列必须有色)- ....
	 
	//X种颜色涂i行(颜色不必都用,每行不必有色,但每列必须有色)已经足够简单,方法数是 [(X+1)^i - 1]^m
	return ans;
}

for (long long i = c, t = 1; i >= 1; i--, t *= -1)
		ans = ((ans + cmb(c, i) * cnt(i) * t) % p + p) % p;
//ans = C种颜色不必都用过(但每行每列必须有色) - C-1种颜色不必都用过(但每行每列必须有色) + C-2种颜色不必都用过(但每行每列必须有色) - .... 

错排问题

f(n)=(n1)(f(n2)+f(n1))

卡特兰数

考虑所有符合条件的路径,一定有 “第一个碰到斜线的地方”。(至少在终点碰到了)

ans= 从起点到这个地方 × 从这个地方到终点。

这两个部分分别是更小的卡特兰数。

当出入栈序列固定,最终序列也就确定了。

出入栈序列只要求每个前缀中,出栈个数小于等于入栈。

这就是卡特兰数。

卡农

简化题意:

给出一个音阶集合 S={x1,x2,...,xn}。记其非空子集(片段)为 S1,S2,...,S2n1

要求选出 m 个非空子集 St1,St2,,Stm,要求每个音阶在选出的非空子集中出现偶数次 且 t1,t2,,tm 互不相同。

问有多少组 {t1,t2,,tm}

三个条件:不同、非空、xi 出现偶数次。

dp[i] 为选 t1ti 并符合条件的方法数。

分步。

  1. 任选 t1ti1,只要求不同非空即可,C2n1i1

  2. ti。对于一个音阶 xa,如果在选出的 t1ti1 中出现奇数次,必须选;否则必须不选。

    考虑特殊情况:ti 和之前的重复了,或者是空集。

    1. Sti 空,意味着前 i1 个已经满足了所有条件。

      因为 “i 个有一个空集” 和 “i1 个刚好满足所有条件” 一一对应,所以一共有 dp[i1] 个这种情况。

    2. Sti 和之前的重复了(显然这种情况和 1 类没有重复),意味着把 Sti 和与它重复的集合去掉,又可以满足所有条件。

      考虑这个对应关系。

      显然 i 个片段含一对重复,可以唯一对应到 dp[i2] 里面。

      但是 dp[i2] 对应到很多个 “i 个含一对”,因为 i2 含的一对相同片段有 [(2n1)(i2)] 种选择。

      所以最后有 dp[i2]×[(2n1)(i2)] 种这个情况。

posted @   FLY_lai  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示