1.GCD与LCM
1.1欧几里得算法求最大公约数
欧几里得算法即在计算机上实现的辗转相除法,多用于求取最大公约数(Greatest Common Divisor),简称GCD。一般用gcd(a,b)来表示 a 与 b 的最大公约数。根据欧几里得算法,可进行如下转换:gcd(a,b)=gcd(a,b%a),代码如下:
在求取GCD时主要分为递归法与递推法,递归法代码简练,递推法则运算速度快。
- /* 递归法 */
- int gcd(int m, int n) {
- return (m==0)?n:gcd(n%m, m);
- }
- /* 迭代法(递推法)*/
- int gcd(int m, int n)
- {
- while(m>0){
- int c = n % m;
- n = m;
- m = c;
- }
- return n;
- }
1.2 LCM(最小公倍数)
最小公倍数的求取是在求得GCD的基础上进一步求得的,假设a,b两个数,他们的最小公倍数为 a*b/gcd(a,b),则代码的书写极其简单:
int lcm(int m,int n){
return m*n/gcd(m,n);
}
2.筛法求素数
void _prime(){
cnt = 1;
memset(check,1,sizeof(check));//初始化认为所有数都为素数
check[0] = check[1] = 0;//0和1不是素数
for(long long i = 2; i <= MAX; i ++){
if(check[i])
prime[cnt++] = i;//保存素数i
for(long long j = 1; j<cnt && prime[j]*i<MAX; j ++){
check[prime[j]*i] = 0;//筛掉小于等于i的素数和i的积构成的合数
if(i % prime[j] == 0)
break;
}
}
}
上述代码中紫色部分最难理解,注释如下:
首先,任何合数都可以表示为若干个素数的乘积,即 i 可表示为 prim[j]* 某个数,prime[j] 可看做其最小质因子。那么当i是 prime[j] 的整数倍,即 i%prime[j]==0 时,i*prime[j+1]肯定也会被筛走,因为 i*prime[j+1] 可看做 prime[j]*某个数*prime[j+1]。因此,当i%prime[j]时,可以终止筛选,没有必要继续进行下去。
3.康托展开 & 逆康托展开
康托展开是一个全排列到一个自然数的双射,常用于构建hash表时的空间压缩。设有n个数(1,2,3,4,…,n),可以有组成不同(n!种)的排列组合,康托展开表示的就是是当前排列组合在n个不同元素的全排列中的名次。这里只粘贴代码,详细请看:
https://blog.csdn.net/wbin233/article/details/72998375#%E7%AE%80%E8%BF%B0
//康托展开
static const int FAC[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; // 阶乘
int cantor(int *a, int n){
int x = 0;
for (int i = 0; i < n; ++i) {
int smaller = 0; // 在当前位之后小于其的个数
for (int j = i + 1; j < n; ++j) {
if (a[j] < a[i])
smaller++;
}
x += FAC[n - i - 1] * smaller; // 康托展开累加
}
return x; // 康托展开值
}
//逆康托展开
static const int FAC[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; // 阶乘
//康托展开逆运算
void decantor(int x, int n)
{
vector<int> v; // 存放当前可选数
vector<int> a; // 所求排列组合
for(int i=1;i<=n;i++)
v.push_back(i);
for(int i=m;i>=1;i--)
{
int r = x % FAC[i-1];
int t = x / FAC[i-1];
x = r;
sort(v.begin(),v.end());// 从小到大排序
a.push_back(v[t]); // 剩余数里第t+1个数为当前位
v.erase(v.begin()+t); // 移除选做当前位的数
}
}