有重复元素的全排列(可重集)
问题描述:有k个元素,其中第i个元素有ni个,求全排列个数。
这个问题,在组合数学中,就是可重集的全排列。
可重集在组合数学上表示为S={a1n1, a2n2, a3n3…aknk}
则S的全排列 = n ! n 1 ! n 2 ! n 3 ! . . . n k ! \frac{n!}{n_1!n_2!n_3!...n_k!} n1!n2!n3!...nk!n!
结论推导过程
推导过程:一共有n个位置
首先考虑第a1号元素,有n1个。
然后所以有C(n, n1)
然后考虑第a2号元素,有n2个。
所以由C(n - n1, n2)
由乘法原理
设X为S的全排列个数
则由乘法原理
X = C(n, n1)C(n - n1, n2)…C(n - n1 - n2…nk-1, nk)
又知
C(n, k) = p(n, k) / k! = n! / n!(m - n)!
代入X等式的右边
得
n
!
n
1
!
(
n
−
n
1
)
!
(
n
−
n
1
)
!
n
1
!
(
n
−
n
1
−
n
2
)
!
…
(
n
−
n
1
−
n
2
−
⋯
−
n
k
)
!
n
k
!
(
n
−
n
1
−
n
2
−
…
n
k
)
!
\frac{n!}{n_1!(n - n_1)!}\frac{(n - n_1)!}{n_1!(n - n_1-n_2)!}\dots\frac{(n - n_1 - n_2 - \dots - n_k)!}{n_k!(n - n_1 - n_2 - \dots n_k)!}
n1!(n−n1)!n!n1!(n−n1−n2)!(n−n1)!…nk!(n−n1−n2−…nk)!(n−n1−n2−⋯−nk)!
约去公因式
即得
X
=
n
!
n
1
!
n
2
!
n
3
!
.
.
.
n
k
!
X = \frac{n!}{n_1!n_2!n_3!...n_k!}
X=n1!n2!n3!...nk!n!
可重复选择的组合
将求的组合数转换成方程的解,这个转换就很有灵性了。
问题描述:有n个不同元素,每个元素元素可以选多次,选k个元素,有多少种方法
方程1 x1 + x2 + x3 + … + xn = k
方程2 y1 + y2 + y3 + … + yn = k + n
其中yi = xi + 1
为什么方程1要转换成2
假设不转换成2,那么存在零解,对于k个1,并不知道取成多少份。
转换成了方程2,每一个yi都必须大于1.。那么k + n必须要分成n个部分,就是在k + n - 1个候选分割线选n - 1,即C(k + n - 1, n - 1) = C(n + k - 1, k).
组合数推导(杨辉三角)
(
a
+
b
)
n
=
∑
C
n
i
a
n
−
k
b
k
(a + b)^n = \sum C_n^ia^{n- k}b^k
(a+b)n=∑Cnian−kbk
组合数推导
C
n
i
=
n
−
k
+
1
k
C
n
n
−
i
C_n^i = \frac{n - k + 1}{k}C_n^{n - i}
Cni=kn−k+1Cnn−i
c[0] = 1;
for(int i = 1; i <= n; ++i)
c[i] = c[i - 1] * (n - i + 1) / i;\
重头戏
约数的个数
Q:给出正整数n的唯一分解式 n = p 1 a 1 p 2 a 2 … p k a k n = p_1^{a_1}p_2^{a_2}\dots p_k^{a_k} n=p1a1p2a2…pkak,求n的正约数的个数。
n的正约数个数为:
∏
(
a
i
+
1
)
=
(
a
1
+
1
)
(
a
2
+
1
)
(
a
3
+
1
)
…
(
a
k
+
1
)
\prod(a_i + 1) = (a_1 + 1)(a_2 + 1)(a_3 + 1)\dots(a_k+1)
∏(ai+1)=(a1+1)(a2+1)(a3+1)…(ak+1)
个人对这条结论的简易理解:
无论pi中的ai取多少,都是n的因数。
对于某个pi,可以取0,1,2…a1
那根据乘法原理
即得上式。
小于n且与n互素的整数个数
ψ
(
x
)
=
∑
(
−
1
)
∣
s
∣
n
∏
p
i
\psi(x) = \sum (-1)^{|s|}\frac{n}{\prod p_i}
ψ(x)=∑(−1)∣s∣∏pin
为什么可以用容斥原理可以得到上面那条公式呢
可以知道,对于唯一分解式
n
=
p
1
a
1
p
2
a
2
…
p
k
a
k
n = p_1^{a_1}p_2^{a_2}\dots p_k^{a_k}
n=p1a1p2a2…pkak其中pi必然都是n的素因子,那首先第一步就是加上n为因子的
−
n
p
1
-\frac{n}{p_1}
−p1n仔细体会一下这个式子就可以了。
那根据容斥,必然有多减的了,所以又要加上
n
p
1
p
2
\frac{n}{p_1p_2}
p1p2n
依次加
(
−
1
)
∣
s
∣
n
p
1
p
2
…
p
k
(-1)^{|s|}\frac{n}{p_1p_2\dots p_k}
(−1)∣s∣p1p2…pkn,|s|指的是分母k的大小
下一步式子我自己顺着推不出了,但是逆着推是可以理解的
ψ
(
n
)
=
n
(
1
−
1
p
1
)
(
1
−
1
p
2
)
…
(
1
−
1
p
k
)
\psi(n) = n(1 - \frac{1}{p_1} )(1 - \frac{1}{p_2})\dots(1 - \frac{1}{p_k})
ψ(n)=n(1−p11)(1−p21)…(1−pk1)
就是每个括号都选一个,乘起来恰好就是第一条式子。
最重要的就是做代码解释的笔记了
代码解释
假设不给出唯一分解式,就要找出素因子。
至于为什么只要找到
n
\sqrt{n}
n
发现对于唯一分解式
n
=
p
1
a
1
p
2
a
2
…
p
k
a
k
n = p_1^{a_1}p_2^{a_2}\dots p_k^{a_k}
n=p1a1p2a2…pkak
如果n不为质数,那么
p
k
<
=
n
p_k <= \sqrt{n}
pk<=n
如果n为质数,那没得好说。
p
k
=
n
p_k = n
pk=n
上述好像是对的,刘汝佳的代码也只是找到了根号n,书上也说要依法判断根号n的就行了。
a
n
s
=
a
n
s
/
i
∗
(
i
−
1
)
ans = ans / i * (i - 1)
ans=ans/i∗(i−1)这个式子是
1
−
1
p
1
1 - \frac{1}{p_1}
1−p11的变形
“除干净”是
while(n % i == 0) n /= i;
这个操作是除以 i,将因数是i的合数同时自己本身又是n的合数的因数剔除。下一个n % i == 0,i就肯定是素因子了。
int euler_phi(int n) {
int m = sqrt(n + 0.5);
for(int i = 2; i <= m;++i) {
if(n % i == 0) {
ans = ans /i * (i - 1);
while(n % i == 0) n /= i;
}
}
if(n > 1) ans = ans / n * (n - 1); //这条语句就是如果n是质数的话,那么for循环不会对结果有作用
}
1~n的所有欧拉函数值
void phi_table(int n, int *phi) {
for(int i = 2; i <= n; ++i) p[i] = 0;
p[1] = 1;
for(int i = 2; i <= n; ++i) {//从2开始,2是素数
if(!phi[i]) { //这句话就判定了i必然是素数,因为嵌套循环里将 <i 的合数都筛了
for(int j = i; j <= n; j += i) {
if(!phi[j]) phi[j] = j;//将因子有i的都筛了
phi[j] = phi[j] / i * (i - 1); //i必然是素数,所以可以相除
}
}
}