算法竞赛入门经典计数基础

有重复元素的全排列(可重集)

问题描述:有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!(nn1)!n!n1!(nn1n2)!(nn1)!nk!(nn1n2nk)!(nn1n2nk)!
约去公因式
即得 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=Cniankbk
组合数推导
C n i = n − k + 1 k C n n − i C_n^i = \frac{n - k + 1}{k}C_n^{n - i} Cni=knk+1Cnni

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=p1a1p2a2pkak,求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)spin
为什么可以用容斥原理可以得到上面那条公式呢
可以知道,对于唯一分解式 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=p1a1p2a2pkak其中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)sp1p2pkn,|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(1p11)(1p21)(1pk1)
就是每个括号都选一个,乘起来恰好就是第一条式子。

最重要的就是做代码解释的笔记了

代码解释

假设不给出唯一分解式,就要找出素因子。
至于为什么只要找到 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=p1a1p2a2pkak
如果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(i1)这个式子是 1 − 1 p 1 1 - \frac{1}{p_1} 1p11的变形
“除干净”是
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必然是素数,所以可以相除
			}
		}
	}		
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Greatljc

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值