组合数学基础笔记

组合数学基础笔记

加乘原理

加法原理

A A A p p p 种方式产生, B B B q q q 种方式产生,则产生 A A A 或B的方式为 p + q p+q p+q

将加法原理推广到 n n n 件事上:事件 A 1 A_1 A1 p 1 p_1 p1 种产生方式,事件 A 2 A_2 A2 p 2 p_2 p2 种产生方式 … \dots 事件 A n A_n An p n p_n pn 种产生方式,则产生 A 1 A_1 A1 A 2 A_2 A2 … \dots A n A_n An p 1 + p 2 + ⋯ + p n p_1+p_2+\dots+p_n p1+p2++pn 种产生方式。

乘法原理

A A A p p p种方式产生, B B B q q q种方式产生,则产生 A A A B B B的方式为$ p*q $。

将加法原理推广到 n n n 件事上:事件 A 1 A_1 A1 p 1 p_1 p1 种产生方式,事件 A 2 A_2 A2 p 2 p_2 p2 种产生方式 … \dots 事件 A n A_n An p n p_n pn 种产生方式,则产生 A 1 A_1 A1 A 2 A_2 A2 … \dots A n A_n An p 1 × p 2 × ⋯ × p n p_1\times p_2\times\dots\times p_n p1×p2××pn 种产生方式。

排列

选排列

n n n 个元素中不重复的选取 m m m ( m ≤ n ) (m\le n) (mn) 并进行排列,不同的排列总数记作 A n m A_n^m Anm。排列中的第一个位置有 n n n 种选择,第二个位置有 n − 1 n-1 n1 种选择 … \dots m m m 个位置有 n − m + 1 n-m+1 nm+1 种选择。所以 A n m = n × ( n − 1 ) × ( n − 2 ) × ⋯ × ( n − m + 1 ) = n ! / ( n − m ) ! A_n^m=n\times(n-1)\times(n-2)\times\dots\times(n-m+1)=n!/(n-m)! Anm=n×(n1)×(n2)××(nm+1)=n!/(nm)! 。读作“ n n n 的降 m m m 阶乘”。

实现:

//复杂度:O(n),基于通项公式
int permutation(int n, int m) {
    int ans = 1;
    for(int i = n; i >= m; i--)
        ans *= i;
    return ans;
}

错位排列

若对于 S ( ∣ S ∣ = k ) S(|S|=k) S(S=k) 第一个排列 q q q ∀ q i ≠ S i ( i ∈ n ∣ { 1 ≤ n ≤ k } ) \forall q_i\neq S_i(i\in n|\{1\le n\le k\}) qi=Si(in{1nk}) 则称 q q q S S S 的错位排列,若 S S S 中任意两个元素互不相等则 S S S 一共有 D ( n ) D(n) D(n)

显然 D ( 1 ) = 0 , D ( 2 ) = 1 D(1)=0,D(2)=1 D(1)=0,D(2)=1 ,现在我们来考察 n = 3 n=3 n=3 的情况

把第 n n n 个元素放在一个位置,比如位置 k k k ,一共有 n − 1 n-1 n1 种方法

放编号为 k k k 的元素,这时有两种情况:

(1)把它放到位置 n n n,那么,对于剩下的 n − 1 n-1 n1 个元素,由于第 k k k 个元素放到了位置 n n n,剩下 n − 2 n-2 n2 个元素就有 D ( n − 2 ) D(n-2) D(n2) 种方法

(2)第 k k k 个元素不把它放到位置 n n n ,这时,对于这 n − 1 n-1 n1 个元素,有 D ( n − 1 ) D(n-1) D(n1) 种方法。

所以 D ( n ) = ( n − 1 ) × [ D ( n − 2 ) + D ( n − 1 ) ] D(n) = (n-1)\times[D(n-2) + D(n-1)] D(n)=(n1)×[D(n2)+D(n1)]

那么我们经行一些推到就可以知道他的通项公式为:

D ( n ) = n ! × ∑ i = 0 n [ ( − 1 ) i i ! ] \displaystyle D(n)=n!\times\sum\limits_{i=0}^n[\frac{(-1)^i}{i!}] D(n)=n!×i=0n[i!(1)i]种排列。

//复杂度O(n)
int derrange(int n) {
	int d[10010] = { 0, 0, 1 };
  	for(int i = 3; i <= n; i++)
    	d[i] = (i - 1) * (d[i - 1] + d[i - 2]);
	return d[n];
}

其实有一个奇奇怪怪的东东:字母 e e e,(自然对数), e = ∑ i = 0 ∞ i ! \displaystyle e=\sum_{i=0}^{\infty}i! e=i=0i!,他有个奇奇怪怪的特性: e − 1 = ∑ i = 0 ∞ [ ( − 1 ) i i ! ] \displaystyle e^{-1}=\sum^{\infty}_{i=0}[\frac{(-1)^i}{i!}] e1=i=0[i!(1)i]

也就是说 D ( n ) ≈ n ! e \displaystyle D(n)\approx \frac{n!}{e} D(n)en!

那他究竟有多准确那?下面是一组数据。

n D ( n ) ≈ D ( n ) = 1 0.36 0 2 0.73 1 3 2.20 2 10 1 , 334 , 960.91 1 , 334 , 961 \begin{matrix} n&D(n)\approx&D(n)=\\ 1&0.36&0 \\ 2&0.73&1 \\ 3&2.20&2 \\ 10&1,334,960.91&1,334,961 \end{matrix} n12310D(n)0.360.732.201,334,960.91D(n)=0121,334,961

使个 r o u n d round round函数,超准的。

//复杂度O(n)
int derrange(int n) {
	double e = 2.7182818284590452353602874713527;
	double ans = 1;
	for (int i = 1; i <= n; i++)
		ans *= (double)i;
	return round(ans / e);
}

圆排列

n n n 个互不相等的元素中选取 m m m 个部分首位的围成一个圆圈的排列叫做圆排列,一共有 A n m m \displaystyle\frac{A_n^m}{m} mAnm种排列。

实现:

//复杂度:O(n),基于通项公式
int circle(int n, int m) {
    int ans = 1;
    for(int i = n; i >= m; i--)
        ans *= i;
    ans /= m;
    return ans;
}

组合

n n n 个元素的集合 S S S 中,无序选出 r r r 个元素,叫做 S S S 的一个r组合。所有不同的组合数个数叫做组合数,记作 C n r C_n^r Cnr ( n r ) \binom{n}{r} (rn) 下面会混着用。
C n r = n ! r ! × ( n − r ) ! \displaystyle C_n^r=\frac{n!}{r!\times(n-r)!} Cnr=r!×(nr)!n!
我们可以推导几个常用的公式:

  1. C n r = C n n − r C_n^r=C_n^{n-r} Cnr=Cnnr

  2. C n r = C n − 1 r + C n − 1 r − 1 C_n^r=C_{n-1}^r+C_{n-1}^{r-1} Cnr=Cn1r+Cn1r1

  3. ∑ i = 0 n C n i = 2 n \sum\limits_{i=0}^nC_n^i=2^n i=0nCni=2n

亲爱的杨辉三角:

0 ≤ k ≤ i 0\le k\le i 0ki

1 i = 0 C i k 1 1 i = 1 C i k 1 2 1 i = 2 C i k 1 3 3 1 i = 3 C i k 1 4 6 4 1 i = 4 C i k 1 5 10 10 5 1 i = 5 C i k 1 6 15 20 15 6 1 i = 6 C i k \begin{matrix} &&&&&&1&&&&&&&i=0&C^k_i \\ &&&&&1&&1&&&&&&i=1&C_i^k \\ &&&&1&&2&&1&&&&&i=2&C_i^k \\ &&&1&&3&&3&&1&&&&i=3&C_i^k \\ &&1&&4&&6&&4&&1&&&i=4&C_i^k \\ &1&&5&&10&&10&&5&&1&&i=5&C_i^k \\ 1&&6&&15&&20&&15&&6&&1&i=6&C_i^k \\ \end{matrix} 111615141513101262013101415151611i=0i=1i=2i=3i=4i=5i=6CikCikCikCikCikCikCik

实现:

//复杂度:O(n),基于通项公式
int combination(int n, m) {
    int ans = 1;
    for(int i = n; i >= m; i--)
        ans *= i;
    for(int i = 1; i <= n - m; i++)
    	ans /= i;
    return ans;
}

P5377鸽鸽的分割

在一个圆周上选 n n n 个不重合的点,将这几个点两两用线段连在一起。问最多能把圆分成多少块。

保证 1 ≤ n ≤ 64 1\le n\le64 1n64

组合数学基础笔记_第1张图片提示:

欧拉公式(降维):

F − E + V = 2 F-E+V=2 FE+V=2

上式中 F F F:平面个数, V V V:点的数量, E E E:边的数量。

解:

欧拉公式:

F − E + V = 2 ⇔ F = E − V + 2 F-E+V=2\Leftrightarrow F=E-V+2 FE+V=2F=EV+2

V = n + ( n 4 ) V=n+\binom{n}{4} V=n+(4n)

E = n + ( n 2 ) + 2 ( n 4 ) E=n+\binom{n}{2}+2\binom{n}{4} E=n+(2n)+2(4n)

∴ F = 3 + ( n 2 ) + ( n 4 ) − 1 \therefore F=3+\binom{n}{2}+\binom{n}{4}-1 F=3+(2n)+(4n)1

注意到平面图包含无限面,而应该计算的是圆内的区域,因此答案应减 1 1 1

故答案为

1 + ( n 2 ) + ( n 4 ) 1+\binom{n}{2}+\binom{n}{4} 1+(2n)+(4n)

时间复杂度: O ( 1 ) O(1) O(1)

鸽笼原理(抽屉原理)

第一抽屉原理:

原理1: 把多于 n n n 个的物体放到 n n n 个抽屉里,则至少有一个抽屉里的东西不少于两件。

证明(反证法):如果每个抽屉至多只能放进一个物体,那么物体的总数至多是 n × 1 n\times 1 n×1,而不是题设的 n + k ( k ≥ 1 ) n+k(k\ge 1) n+k(k1) ,故不可能。

原理2:把多于 m n + 1 ( n ≠ 0 ) mn+1(n\ne 0) mn+1(n=0) 个的物体放到 n n n 个抽屉里,则至少有一个抽屉里有不少于 ( m + 1 ) (m+1) (m+1) 的物体。

证明(反证法):若每个抽屉至多放进 m m m 个物体,那么 n n n 个抽屉至多放进 m × n m\times n m×n 个物体,与题设不符,故不可能。

原理3:把无数多件物体放入 n n n 个抽屉,则至少有一个抽屉里有无数个物体。

第二抽屉原理:

( m × n − 1 ) (m\times n-1) (m×n1) 个物体放入 n n n 个抽屉中,其中必有一个抽屉中至多有 ( m − 1 ) (m-1) (m1) 个物体。

证明(反证法):让每个抽屉都放 m m m 个,则有 ( m × n − 1 ) ÷ m = n − 1 … m − 1 (m\times n-1)\div m = n-1 \dots m-1 (m×n1)÷m=n1m1 只能放 n − 1 n-1 n1 个抽屉,多出来 m − 1 m-1 m1 个,所以必定有一个抽屉中至多有 m − 1 m-1 m1 个。

P7107天选之人

n n n 个人, n × m n\times m n×m 个东西,其中 k k k 个东西有记号,每人随机拿 m m m 个东西。

一个人抽到最多的记号,当且仅当没有人抽到的记号比他还多。是否可能 p p p 个人抽到最多的记号。如果有可能,你需构造每个人抽的纸团中分别有多少带记号、有多少不带记号。

形式化地,假设第i个人抽到了 x i x_i xi 张带记号的纸团和 y i y_i yi 张不带记号的纸团,你的构造应满足: x i , y i ≥ 0 x_i, y_i \ge 0 xi,yi0 x i + y i = m x_i+y_i=m xi+yi=m

保证: 1 ≤ p ≤ n ≤ 10 5 1\le p\le n\le{10}^5 1pn105 1 ≤ m ≤ 10 9 1\le m\le{10}^9 1m109 0 ≤ k ≤ n m 0\le k\le n m 0knm

解:

先记 max ⁡ i = 1 n x i = q \displaystyle \max\limits_{i=1}^nx_i=q i=1maxnxi=q

首先,我们先考虑满足一共有 p p p j j j 使得 x j = max ⁡ i = 1 n x i \displaystyle x_j=\max\limits_{i=1}^nx_i xj=i=1maxnxi

这个条件,由于一共最多只能有 p p p 个这样的最大值,于是我们可以得到这个最大值 q ≤ min ⁡ { m , k / p } q \leq\min\{m,k/p\} qmin{m,k/p},再取大,就会导致 p × q > k × p × q > k \displaystyle p\times q>k\times p\times q>k p×q>k×p×q>k,不满足题意。

为了简化操作,我们直接让 q = min ⁡ { m , k / p } \displaystyle q=\min\{m,k/p\} q=min{m,k/p}

于是我们先让 x 1 ∼ p = q \displaystyle x_{1 \sim p}=q x1p=q y 1 ∼ p = m − q × x y_{1 \sim p}=m-q\times x y1p=mq×x

然后,我们定义 r e s t = k − p × q rest=k-p\times q rest=kp×q,表示剩余有标记的纸团的个数,又要保证 max ⁡ i = p + 1 n x i < q \displaystyle\max\limits_{i=p+1}^n x_i < q i=p+1maxnxi<q 所以我们考虑在一定有解且 r e s t > 0 rest > 0 rest>0 的情况下,剩下的 x i x_i xi 1 ∼ q − 1 1\sim q−1 1q1 最合适。

当第 h h h 个人取 q − 1 q−1 q1 时, r e s t − ( q − 1 ) < 0 rest-(q-1)<0 rest(q1)<0,那么直接把剩下所有的 r e s t rest rest 都给这个 h h h,至此,所有有记号的纸条都已经分发完毕。

于是剩下的就直接取 x i = 0 x_i=0 xi=0,即 ∀   h < i ≤ n , x i = 0 , y i = m \displaystyle\forall\ h  h<in,xi=0,yi=m 至此,数列 x x x y y y 就构造完毕了,而且复杂度也是 O ( n ) O(n) O(n) 的。

以上仅为有解情况,还需要考虑无解情况。

无解情况就是 p p p q q q 分配完后所剩余的 r e s t rest rest 分配给剩下的 n − p n-p np 个人每人 q − 1 q−1 q1 后仍有剩余,则我们是找不到这样的 q q q 的。

用式子表示即为 k − p × q > ( n − p ) × ( q − 1 ) k-p\times q>(n-p)\times (q-1) kp×q>(np)×(q1)

总的复杂度为 O ( n ) O(n) O(n)

容斥

DeMorgan 定理:设 A A A B B B为全集 U U U的任意两个子集,则 A ∩ B ‾ = A ‾ ∪ B ‾ \displaystyle\overline{A\cap B}=\overline A \cup \overline B AB=AB A ∪ B ‾ = A ‾ ∩ B ˉ \overline{A \cup B}=\overline A \cap \bar B AB=ABˉ

组合数学基础笔记_第2张图片

DeMorgan 定理推广到 n n n 个子集:设 A 1 , A 2 , … , A n A_1,A_2,\dots,A_n A1,A2,,An 为全集 U U U 的任意 n n n 个子集,

A 1 ∩ A 2 ∩ ⋯ ∩ A n ‾ = A ‾ 1 ∪ A ‾ 2 ∪ ⋯ ∪ A ‾ n \displaystyle\overline{A_1 \cap A_2 \cap \dots \cap A_n}=\overline A_1 \cup \overline A_2 \cup \dots \cup \overline A_n A1A2An=A1A2An

A 1 ∪ A 2 ∪ ⋯ ∪ A n ‾ = A 1 ‾ ∩ A 2 ‾ ∩ ⋯ ∩ A n ‾ \displaystyle\overline{A_1 \cup A_2 \cup \dots \cup A_n}=\overline{A_1} \cap \overline{A_2} \cap \dots \cap \overline{A_n} A1A2An=A1A2An

容斥原理:设 S S S 为有穷集, P 1 , P 2 , … , P n P_1,P2,\dots ,P_n P1,P2,,Pn n n n 条性质。 S S S 中任意元素 x x x 对于这 n n n 条性质可能符合其中的一种、两种、 … \dots n n n 种,也可能不符合。设 A i A_i Ai 表示 S S S 中具有 P i P_i Pi 性质的元素构成的子集。有限集合 A A A 的势记作 ∣ A ∣ |A| A 读作“ A A A 的势”(可以理解为 A A A 中元素的数量),则:
(1):S中不具有: P 1 , P 2 , … , P n P_1,P_2, \dots ,P_n P1,P2,,Pn 的元素有:

∣ A 1 ‾ ∩ A 2 ‾ ∩ ⋯ ∩ A n ‾ ∣ \displaystyle|\overline{A_1} \cap \overline{A_2} \cap \dots \cap \overline{A_n}| A1A2An

= ∣ S ∣ − ∑ i ∣ A i ∣ + ∑ i < j ∣ A i ∩ A j ∣ − ∑ i < j < k ∣ A i ∩ A j ∩ A k ∣ + ⋯ + ( − 1 ) n ∣ A 1 ∩ A 2 ∩ ⋯ ∩ A n ∣ =\displaystyle|S|-\sum\limits_i|A_i|+\sum\limits_{i=SiAi+i<jAiAji<j<kAiAjAk++(1)nA1A2An

(2):S中具有性质 P 1 , P 2 , … , P n P_1,P_2, \dots ,P_n P1,P2,,Pn 的元素为: ∣ A 1 ∣ + ∣ A 2 ∣ + ∣ A 3 ∣ + ⋯ + ∣ A n ∣ \displaystyle|A_1|+|A_2|+|A_3|+ \dots +|A_n| A1+A2+A3++An

= ∑ i ∣ A i ∣ − ∑ i < j ∣ A i ∩ A j ∣ + ∑ i < j < k ∣ A i ∩ A j ∩ A k ∣ − ⋯ − ( − 1 ) n + 1 ∣ A 1 ∩ A 2 ∩ ⋯ ∩ A n ∣ \displaystyle=\sum\limits_i|A_i|-\sum\limits_{i=iAii<jAiAj+i<j<kAiAjAk(1)n+1A1A2An

P3197越狱

一个序列共 n n n 个物品,物品共有 m m m 个,问有多少种情况在这个序列中有两个相邻的物品种类一样。

答案对 100 , 003 100,003 100,003 取模。

保证: 1 ≤ m ≤ 1 0 8 1\le m\le 10^8 1m108 1 ≤ n ≤ 1 0 12 1\le n\le10^{12} 1n1012

解:

所有状态的计算:
一个物品有 m m m 种状态,在不考虑其它条件的情况下,两个物品有 m 2 m^2 m2 种状态 ⇒ n \Rightarrow n n 个物品有 m n m^n mn 种总状态。

相邻房间不同种类的状态的计算:
取最左边的物品,得该物品可能信仰的种类数量是 m m m

设该物品选了种类 j j j ,那么在他右边的物品的可供选择就只剩下 ∣ { 1 … m } − { j } ∣ = m − 1 |\{1 \dots m\}-\{j\}|=m-1 {1m}{j}=m1 种了

那么再以这个选 m − 1 m−1 m1 种种类的物品为标准,设他随便选了种类 k ( k ≠ j ) k (k\ne j) k(k=j), 他的左边肯定跟他不一样,所以只要他右边也跟他不一样即可

那么右边剩下的选项为 ∣ { 1 … m } − { k } ∣ = m − 1 |\{1 \dots m\}-\{k\}|=m-1 {1m}{k}=m1

可以看到,对于任意一名物品,他需要与他左边相邻的物品不同; 所以如果只考虑左边,他的种类可选数量为(总数量 − - 左边相邻物品已选择种类的数量(显然是1个嘛) = m − 1 = m-1 =m1

而递推过去,他的右边相邻物品会避免选择他选择的种类,因而我们不用把相邻右边的物品考虑进去。 所以情况数就为 m × ( m − 1 ) n − 1 m\times(m-1)^{n-1} m×(m1)n1

最终答案
两者相减,得到 m n − m × ( m − 1 ) n − 1 m^{n}-m\times (m-1)^{n-1} mnm×(m1)n1

总时间复杂度 O ( 1 ) O(1) O(1)

P6298齿轮

Daniel13265 从不知哪里找来了 n n n 个齿轮,第 i i i 个齿轮的齿数为不超过 m m m 的正整数 a i a_i ai。他现在想把其中 k k k 个齿轮按照一定的方式拼接在一起。

当齿轮使用一段时间后,就会产生损耗。一个齿轮组的损耗速率是由这个齿轮组的所有齿轮齿数的最大公约数决定的:最大公约数越大,相同的齿之间啮合的频率就会增高,从而损耗的速率就会变快。这个最大公约数又被称为损耗因子。

算出一个齿轮组的损耗因子是很容易的。可是现在 Daniel13265 想要知道,对于可能拼接出的所有齿轮组的损耗因子。

Daniel13265 知道拼接出损耗因子大于 m m m 的齿轮组是不可能的,而且由于可能拼出的齿轮组的个数很多,你只需要反过来告诉他对于所有的 t ∈ [ 1 , m ] t\in[1, m] t[1,m],能够拼接出的损耗因子为t的齿轮组的个数对 1 0 9 + 7 10^9+7 109+7 取模后的结果即可。

保证: 1 ≤ k ≤ n ≤ 1 0 6 1\le k\le n\le10^6 1kn106 1 ≤ a i ≤ m ≤ 1 0 6 1\le a_i\le m\le10^6 1aim106

解:

我们设 g i g_i gi 表示选 k k k 个数,它们的 gcd ⁡ \gcd gcd i i i 的倍数的方案数。

显然有:

g i = ( ∑ i = 1 n [ i ∣ a i ] k ) \displaystyle g_i=\binom {\sum_{i=1}^n [i|a_i]}{k} gi=(ki=1n[iai])

不仅包括了 gcd ⁡ \gcd gcd i i i 的情况,还包括了 gcd ⁡ \gcd gcd 2 i , 3 i , … 2i,3i,\ldots 2i,3i, 的情况。我们可以容斥。倒序枚举 i i i ,将 g i g_i gi 的值,减去 g 2 i , g 3 i , … g_{2i},g_{3i},\ldots g2i,g3i, 的值即可。

时间复杂度:
g i g_i gi 的过程可以做到 O ( m log ⁡ m ) O(m \log m) O(mlogm),组合数可以直接预处理阶乘的逆元,时间复杂度 O ( n ) O(n) O(n)

总时间复杂度 O ( m log ⁡ m + n ) O(m \log m+n) O(mlogm+n)

P6692出生点

n × m n\times m n×m 的网格,有 k k k 个障碍点,随机取两个不一样且不为障碍点的点,求出所有情况中他们两人距离之和。(这里的距离指两点间曼哈顿距离,即 ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ \displaystyle\left|x_1-x_2\right|+\left|y_1-y_2\right| x1x2+y1y2)

保证: 1 ≤ n , m ≤ 1 0 9 , 1 ≤ x i ≤ n , 1 ≤ y i ≤ m , 0 ≤ k ≤ 5 × 1 0 5 , k < n × m 1\leq n,m\leq 10^9,1\leq x_i\leq n,1\leq y_i\leq m,0\leq k\leq 5\times 10^5,k1n,m109,1xin,1yim,0k5×105,k<n×m,所有障碍点各不相同

组合数学基础笔记_第3张图片
解:

所有格子两两的距离之和

我们将各格子的横坐标与纵坐标写下:

( 1 , 1 ) , ( 1 , 2 ) , ( 1 , 3 ) , ( 1 , 4 ) … ( 1 , m ) , ( 2 , 1 ) , ( 2 , 2 ) … ( 2 , m ) … ( n , m − 1 ) , ( n , m ) , ( 1 , 1 ) , ( 1 , 2 ) ( 1 , 3 ) , ( 1 , 4 ) … ( 1 , m ) , ( 2 , 1 ) , ( 2 , 2 ) … ( 2 , m ) … ( n , m − 1 ) , ( n , m ) (1,1),(1,2),(1,3),(1,4)\dots(1,m),(2,1),(2,2)\dots(2,m)\dots(n,m-1),(n,m),(1,1),(1,2)(1,3),(1,4)\dots(1,m),(2,1),(2,2)\dots(2,m)\dots(n,m-1),(n,m) (1,1),(1,2),(1,3),(1,4)(1,m),(2,1),(2,2)(2,m)(n,m1),(n,m),(1,1),(1,2)(1,3),(1,4)(1,m),(2,1),(2,2)(2,m)(n,m1),(n,m)

然后将横坐标与纵坐标拆开,其依据是加法分配律。

横坐标: 1 , 1 , 1 , 1 … 1 , 2 , 2 … 2 … n , n 1 , 1 , 1 , 1 … 1 , 2 , 2 … 2 … n , n 1,1,1,1 \dots 1,2,2 \dots 2 \dots n,n1,1,1,1 \dots 1,2,2 \dots 2 \dots n,n 1,1,1,11,2,22n,n1,1,1,11,2,22n,n

纵坐标: 1 , 1 , 1 , 1 … 1 , 2 , 2 … 2 … m , m 1 , 1 , 1 , 1 … 1 , 2 , 2 … 2 … m , m 1,1,1,1 \dots 1,2,2 \dots 2 \dots m,m1,1,1,1 \dots 1,2,2 \dots 2 \dots m,m 1,1,1,11,2,22m,m1,1,1,11,2,22m,m

可以发现,横坐标的计算方式与纵坐标的计算方式基本相同,可以类比,这里只讨论横坐标的计算方式。

首先,可以发现,存在 m m m 1 1 1 m m m 2 … m 2 \dots m 2m n n n。于是,我们为了简化这个序列,我们将这个序列去重,同时答案要乘上 m 2 m^2 m2。之所以要乘上 m 2 m^2 m2,是因为,例如对于去重后的二元组 ( 1 , 2 ) (1,2) (1,2),其差 2 − 1 = 1 2-1=1 21=1 被贡献的次数是1出现的次数( m m m 次)与 2 2 2 出现的次数( m m m 次)之积,即 m 2 m^2 m2 次。

我们现在的序列是 1 , 2 … n 1 , 2 … n 1,2 \dots n1,2 \dots n 1,2n1,2n,现在需要求出其中任意两个数之差的和。考虑每个数对答案的贡献,例如 5 5 5 对答案的贡献为 ( 5 − 4 ) + ( 5 − 3 ) + ( 5 − 2 ) + ( 5 − 1 ) (5-4)+(5-3)+(5-2)+(5-1) (54)+(53)+(52)+(51)(简述为"前贡献")与 ( n − 5 ) + ( n − 1 − 5 ) + ( n − 2 − 5 ) + … … + 1 (n-5)+(n-1-5)+(n-2-5)+……+1 (n5)+(n15)+(n25)+……+1(简述为"后贡献")。可以发现,任何一个数的前贡献一定与另一个数的后贡献相同,如k的前贡献与 n + 1 − k n+1-k n+1k 的后贡献相同,则这里的答案就是所有前贡献和的两倍。

于是,现在思考如何计算前贡献。如一个数为 i i i ,那么它的前贡献为 ( i − 1 ) + ( i − 2 ) + … … + 2 + 1 \displaystyle(i-1)+(i-2)+……+2+1 (i1)+(i2)+……+2+1,即

( i − 1 ) i − ( 1 + 2 + 3 + … … + ( i − 1 ) ) \displaystyle(i-1)i-(1+2+3+……+(i-1)) (i1)i(1+2+3+……+(i1))

= ( i − 1 ) i − i ( i − 1 ) 2 \displaystyle=(i-1)i-\frac {i(i-1)} 2 =(i1)i2i(i1)

= i ( i − 1 ) 2 \displaystyle=\frac {i(i-1)} 2 =2i(i1)

其中第二步用到的是等差数列求和公式。

现在,把这个可爱的式子带入答案的式子:

2 × 1 + 3 × 2 + 4 × 3 + … … + n ( n − 1 ) 2 \displaystyle\frac {2×1+3×2+4×3+……+n(n-1)} 2 22×1+3×2+4×3+……+n(n1)

= ( 1 2 + 2 2 + 3 2 + … … + ( n − 1 ) 2 ) + ( 1 + 2 + 3 + … … + ( n − 1 ) ) 2 \displaystyle=\frac {(1^2+2^2+3^2+……+(n-1)^2)+(1+2+3+……+(n-1))} 2 =2(12+22+32+……+(n1)2)+(1+2+3+……+(n1))

= ( n − 1 ) n ( 2 n − 1 ) 6 + ( n − 1 ) n 2 2 \displaystyle=\frac {\frac {(n-1)n(2n-1)} 6 + \frac {(n-1)n} 2} 2 =26(n1)n(2n1)+2(n1)n

其中,第二步使用了平方和公式,即 1 2 + 2 2 + … … + n 2 = n ( n + 1 ) ( 2 n + 1 ) 6 \displaystyle1^2+2^2+……+n^2=\frac {n(n+1)(2n+1)} 6 12+22+……+n2=6n(n+1)(2n+1),第三步的简单化简过程略。

所以说,整个序列 1 , 2 , 3 … , n 1 , 2 , 3 … , n 1,2,3 \dots ,n1,2,3 \dots ,n 1,2,3,n1,2,3,n 的任意两个数之差的和就是 2 × ( n − 1 ) n ( n + 1 ) 6 = ( n − 1 ) n ( n + 1 ) 3 \displaystyle2×\frac {(n-1)n(n+1)} 6=\frac {(n-1)n(n+1)} 3 2×6(n1)n(n+1)=3(n1)n(n+1)

所以说,原序列 1 , 1 , 1 … , 2 , 2 … n , n 1,1,1 \dots ,2,2 \dots n,n 1,1,1,2,2n,n 的任意两个数之差的和是 $\displaystyle\frac {(n-1)n(n+1)} 3×m^2 $。

类比得到,方格中任意两个格子的哈密尔顿路径的长度之和就是:

( n − 1 ) n ( n + 1 ) 3 × m 2 + ( m − 1 ) m ( m + 1 ) 3 × n 2 \displaystyle\frac {(n-1)n(n+1)} 3×m^2+\frac {(m-1)m(m+1)} 3×n^2 3(n1)n(n+1)×m2+3(m1)m(m+1)×n2

求出两个格子中有一个是障碍物的距离之和:

此时宏观做法显然,分别枚举每个障碍点,然后直接求出它与其他格子的距离之和。假设目前看到了障碍点 ( x , y ) (x,y) (x,y),那么它与其他格子的距离之和就可以按如下方式计算:

同样,将横坐标与纵坐标拆开。于是,现在我们思考的是 x x x 对答案的贡献, y y y 可类比得到。

可以发现, x x x 对答案的贡献可以通过第一部分所述的"前贡献"与"后贡献"得到,这里不再论述。注意, x x x 的贡献值还要乘上一个 m m m,即 x x x 1 1 1 的贡献是 1 1 1 出现的次数,即 m m m 次。

求出障碍点中任意两个点的距离之和:

同样将横坐标与纵坐标拆开;这里只讨论横坐标的计算方式,纵坐标可以类比。假设排序后的障碍点的横坐标为 a 1 , a 2 , … a k a_1,a_2, \dots a_k a1,a2,ak,那么其中任意两个数之差的和同样也可以通过计算贡献得到。设该数组 a a a 的前缀和为 p r e pre pre,后缀和为 s u f suf suf,则 a i a_i ai对答案的贡献就是:

前贡献: ( i − 1 ) a i − p r e i − 1 \displaystyle(i-1)a_i-pre_{i-1} (i1)aiprei1

后贡献: s u f i + 1 − ( n − i ) a i \displaystyle suf_{i+1}-(n-i)a_i sufi+1(ni)ai

总时间复杂度: O ( k l o g 2 k ) O(klog_2k) O(klog2k)

二项式定理

定理定义:
( x + y ) n = ∑ k = 0 n ( n k ) x k y n − k = ∑ k = 0 n ( n k ) x n − k y k \displaystyle(x+y)^n=\sum\limits_{k=0}^n\binom{n}{k}x^ky^{n-k}=\sum\limits_{k=0}^n\binom{n}{k}x^{n-k}y^k (x+y)n=k=0n(kn)xkynk=k=0n(kn)xnkyk

矩阵形式:
( a + b ) n = [ a 0 … a n ] × [ C n 0 ⋱ C n n ] [ 1 ⋮ 1 ] [ b 0 ⋮ b n ] n ∈ N + \displaystyle(a+b)^n=[a^0 \dots a^n]\times \left[\begin{matrix}C_n^0 & & \\ & \ddots & \\ & & C_n^n \end{matrix} \right] \left[\begin{matrix} & & 1\\ & \vdots & \\ 1 & & \end{matrix} \right] \left[\begin{matrix}b^0\\ \vdots \\ b^n \end{matrix}\right] n \in N^+ (a+b)n=[a0an]× Cn0Cnn 11 b0bn nN+
(矩阵的可以不理解)

斐波那契数列

定义:

递推式: f ( x ) = f ( x − 1 ) + f ( x − 2 ) ( f ( 1 ) = 1 , f ( 2 ) = 1 ) \displaystyle f(x)=f(x-1)+f(x-2) (f(1)=1,f(2)=1) f(x)=f(x1)+f(x2)(f(1)=1,f(2)=1)

通项公式: f ( x ) = 5 5 [ ( 1 + 5 2 ) x − ( 1 − 5 2 ) x ] \displaystyle f(x)=\frac{\sqrt 5}{5}[(\frac{1+\sqrt 5}{2})^x-(\frac{1-\sqrt 5}{2})^x] f(x)=55 [(21+5 )x(215 )x]

简单证明:

f ( x ) − k × f ( x − 1 ) = − 1 k × [ f ( x − 1 ) − k × f ( x − 2 ) ] \displaystyle f(x)-k\times f(x-1)=-\frac{1}{k}\times{[f(x-1)-k\times f(x-2)]} f(x)k×f(x1)=k1×[f(x1)k×f(x2)],其中 k = 1 + 5 2 \displaystyle k=\frac{1+\sqrt5}2 k=21+5 1 − 5 2 \displaystyle\frac{1-\sqrt5}2 215

f ( x ) − k × f ( x − 1 ) = ( − 1 k ) x − 2 × ( 1 − k ) f(x)-k\times f(x-1)=(-\frac{1}{k})^{x-2}\times(1-k) f(x)k×f(x1)=(k1)x2×(1k)

到这里学过高中课程的同学应该都会算了吧

最后算得 f ( x ) = 5 5 [ ( 1 + 5 2 ) x − ( 1 − 5 2 ) x ] \displaystyle f(x)=\frac{\sqrt 5}{5}[(\frac{1+\sqrt 5}{2})^x-(\frac{1-\sqrt 5}{2})^x] f(x)=55 [(21+5 )x(215 )x]

//复杂度:O(n),基于递推公式
int fibonacci(int index) {
	if(index <= 0)
    	return -1;
    if(index <= 2)
    	return 1;
    int array[100];
    array[1] = array[2] = 1;
    for(int i = 3; i <= index; i++)
    	array[i] = array[i - 1] + arry[i - 2];
    return array[index];
}

顺便提亿句:斐波那契可以用矩阵快速幂做,复杂度 O ( log ⁡ n ) O(\log n) O(logn)(如下)。

[ f ( x ) f ( x − 1 ) ] = [ f ( x − 1 ) f ( x − 2 ) ] × [ 1 1 1 0 ] \left[\begin{matrix}f(x) & f(x-1)\end{matrix}\right]=\left[\begin{matrix}f(x-1)&f(x-2)\end{matrix}\right]\times \left[\begin{matrix}1&1\\1&0\end{matrix}\right] [f(x)f(x1)]=[f(x1)f(x2)]×[1110]

康托展开

X = a n ( n − 1 ) ! + a n − 1 ( n − 2 ) ! + ⋯ + a 1 × 0 X=a_n(n-1)!+a_{n-1}(n-2)!+\dots+a_1\times0 X=an(n1)!+an1(n2)!++a1×0

其中 a i a_i ai 为整数,并且 0 ≤ a i ≤ i , 1 ≤ i ≤ n 0\le a_i\le i,1\le i\le n 0aii,1in

a i a_i ai 表示原数的第 i i i 位在当前未出现的元素中是排在第几个

康托展开逆运算:

既然康托展开是一个双射,那么一定可以通过康托展开值求出原排列,即可以求出 n n n 的全排列中第x大排列。
n = 5 , x = 96 n=5,x=96 n=5,x=96 时:

首先用 96 − 1 96-1 961 得到 95 95 95,说明 x x x 之前有 95 95 95 个排列。(将此数本身减去 1 1 1)用 95 95 95 去除 4 ! 4! 4! 得到 3 3 3 23 23 23,说明有 3 3 3 个数比第 1 1 1 位小,所以第一位是 4 4 4。用 23 23 23 去除 3 ! 3! 3! 得到 3 3 3 5 5 5,说明有 3 3 3 个数比第 2 2 2 位小,所以是 4 4 4,但是 4 4 4 已出现过,因此是 5 5 5。用 5 5 5 去除 2 ! 2! 2! 得到 2 2 2 1 1 1,类似地,这一位是 3 3 3。用 1 1 1 去除 1 ! 1! 1! 得到 1 1 1 0 0 0,这一位是 2 2 2。最后一位只能是 1 1 1。所以这个数是 45321 45321 45321

例:

在5个数的排列组合中,计算 34152 34152 34152 的在字典序中的第几个。

解:

首位是 3 3 3,则小于 3 3 3 的数有两个,为 1 1 1 2 2 2,则首位小于 3 3 3 的所有排列组合为 4 ! 4! 4!

第二位是 4 4 4 ,由于第一位小于 4 4 4 所以 1 , 2 , 3 1,2,3 1,2,3 中一定会有一个充当第一位,所以排在 4 4 4 之下的只剩两个,所以其实计算的是在第二位之后小于 4 4 4 的个数。

因此:

第三位是 1 1 1,则在其之后小于 1 1 1 的数有 0 0 0 个。所以第四位是 5 5 5,则在其之后小于 5 5 5 的数有 1 1 1 个,为 2 2 2。所以最后一位就不用计算啦,因为在它之后已经没有数了,所以固定为 0 0 0

根据公式:

X = 2 × 4 ! + 2 × 3 ! + 0 × 2 ! + 1 × 1 ! + 0 × 0 ! X=2\times4!+2\times3!+0\times2!+1\times1!+0\times0! X=2×4!+2×3!+0×2!+1×1!+0×0!

所以比 34152 34152 34152 小的组合有 61 61 61 个,即 34152 34152 34152 是排第 62 62 62

例:

即对于上述例子,在给出某序列在第 61 61 61 个。

解:

61 4 ! = 2 … 13 \displaystyle\frac{61}{4!} = 2\dots13 4!61=213,说明 a [ 5 ] = 2 a[5]=2 a[5]=2,说明比首位小的数有两个,所以首位为 3 3 3

13 3 ! = 2 … 1 \displaystyle\frac{13}{3!} = 2\dots1 3!13=21,说明 a [ 4 ] = 2 a[4]=2 a[4]=2,说明在第二位之后小于第二位的数有两个,所以第二位为 4 4 4

1 2 ! = 0 … 1 \displaystyle\frac{1}{2!} = 0\dots1 2!1=01,说明 a [ 3 ] = 1 a[3]=1 a[3]=1,说明在第三位之后没有小于第三位的数,所以第三位为 1 1 1

1 1 ! = 1 … 0 \displaystyle\frac{1}{1!} = 1\dots0 1!1=10,说明 a [ 2 ] = 0 a[2]=0 a[2]=0,说明在第四位之后小于第四位的数有 1 1 1 个,所以第四位为 5 5 5

最后一位自然就是剩下的数 2 2 2

通过以上分析,所求排列组合为 34152 34152 34152

P3014Cow Line S

N ( 1 < = N < = 20 ) N(1<=N<=20) N(1<=N<=20) 头牛,编号为 1... N 1...N 1...N,正在与 FJ 玩一个疯狂的游戏。奶牛会排成一行(牛线),问 FJ 此时的行号是多少。之后,FJ 会给牛一个行号,牛必须按照新行号排列成线。

行号是通过以字典序对行的所有排列进行编号来分配的。比如说:FJ 有 5 5 5 头牛,让他们排为行号 3 3 3 ,排列顺序为:

1 : 1 2 3 4 5 2 : 1 2 3 5 4 3 : 1 2 4 3 5 \begin{matrix} 1:1 & 2 & 3 & 4 & 5\\ 2:1 & 2 & 3 & 5 & 4\\ 3:1 & 2 & 4 & 3 & 5 \end{matrix} 1:12:13:1222334453545

因此,牛将在牛线 12435 1 2 4 3 5 12435 中。

之后,奶牛排列为“ 12534 1 2 5 3 4 12534”,并向 FJ 问他们的行号。继续列表:

4 : 1 2 4 5 3 5 : 1 2 5 3 4 \begin{matrix} 4:1&2&4&5&3\\ 5:1&2&5&3&4 \end{matrix} 4:15:122455334

FJ 可以看到这里的答案是 5 5 5

FJ 和奶牛希望你的帮助玩他们的游戏。他们需要 K ( 1 ≤ K ≤ 10000 ) K(1\le K\le 10000) K(1K10000) 组查询,查询有两个部分: C i C_i Ci 将是“P”或“Q”的命令。

如果 C i C_i Ci 是’P’,则查询的第二部分将是一个整数 A i ( 1 ≤ A i ≤ N ! ) A_i(1\le A_i\le N!) Ai(1AiN!),它是行号。此时,你需要回答正确的牛线。

如果 C i C_i Ci 是“Q”,则查询的第二部分将是 N N N 个不同的整数 B i j ( 1 ≤ B i j ≤ N ) B_{ij}(1\le B_{ij}\le N) Bij(1BijN)。这将表示一条牛线,此时你需要输出正确的行号。

解:

基本的康托展开及其逆运算

实现:

int n;
void reverse_cantor(int x){
	memset(vis, 0, sizeof vis);
	x--;
	int j;
	for(int i = 1; i <= n; i++){
		int t = x / fac[n - i];
		for(j = 1; j <= n; j++){
			if(!vis[j]){
				if(!t)
					break;
				t--;
			}
		}
		printf("%d ", j);
		vis[j] = 1;
		x %= fac[n - i];
	}
	puts("");
}
int cantor(int x[]){
	int p = 0;
	for(int i = 1; i <= n; i++){
		int t = 0;
		for(int j = i + 1; j <= n; j++)
			if(x[i] > x[j])
				t++;
		p += t * fac[n - i];
	}
	return p + 1;
}

你可能感兴趣的:(笔记,算法)