-
试除法判定素数
素数:一个数x除了1和这个数本身外,无其他约数 可通过枚举i∈(2, x-1) 若有 x % i ==0 则说明此数不是质数。
bool is_Prime(int n)
{
for(int i = 2; i < n; i ++)
if(x % i == 0)
return false;
return true;
}
考虑到若x%i==0,则i能整除x,则x/i也能整除x,并且i和x/i都∈(2,x), 所以上述代码在x不是素数时进行不必要的枚举,只枚举两数中的较小的哪一个即可,即满足i <= x / i
bool is_Prime(int n)
{
for(int i = 2; i <= n / i; i ++)
if(n % i == 0) return false;
return true;
}
此时注意到for循环的范围也有另外两种写法 :
1. i < sqrt(n) 但这种写法的sqrt函数较慢
2. i * i <= n 这种写法在i的数值很大时, i*i会爆int
所以范围写 i <= n / i 最为妥当
-
分解质因子
将一个数分解成其所有的质因子,并统计质因子的个数
unordered_map<int, int> hash;
void break_down(int n)
{
for(int i = 2; i <= n / i; i ++) // @ a
{
if(n % i == 0) // @ b
{
int res = 0;
while(n % i == 0)
{
n /= i;
hash[i] ++;
}
cout << i << " " << hash[i] << endl;
}
}
if(n > 1) hash[n] ++; // @ c
cout << n << " " << hash[n] << endl;
}
需要解释的三处: a, 一个数n最多只有一个大于sqrt(n)的质因子,所以条件范围可写为i <= n / i
b, 如果满足if语句的判断则说明i一定是质数,证明:循环到这一步说明n中所有∈(2, i - 1) 的质因子已经被去除,若i不是质数,则i一定含有∈(2, i - 1)的质因子,此时n % i !=0 ,与if条件矛盾,故若满足if语句则i一定是质数
c, 循环结束若n>1说明n存在大于sqrt(n)的质因子且只有一个
可根据分解质因子进一步拓展到求解一个数的约数的个数和约数的和的问题。设一个数n的质因子集合为{p1, p2, p3 ……pk}, 它们的个数集合为{a1, a2, a3,……, ak},(p1^a1 * p2 ^ a2 * p3 ^ a3 ……pk ^ ak = n), 对于不同质数每一种指数的不同组合都会生成不同的n的一个约数,共有(a1 + 1)*(a2 + 1)*(a3 + 1)*(a4 + 1)……(a5 + 1)个约数
unordered_map<int, int> hash;
void break_down(int n)
{
for(int i = 2; i <= n / i; i ++)
{
if(n % i == 0)
{
int res = 0;
while(n % i == 0)
{
n /= i;
hash[i] ++;
}
cout << i << " " << hash[i] << endl;
}
}
if(n > 1) hash[n] ++;
cout << n << " " << hash[n] << endl;
}
int sum = 1;
for(auto h : hash)
{
int t = h.second;
sum = sum * (h.second + 1);
}
求和 将所有组合的指数的结果加起来就是和 即(p1 ^ 0 +p1 ^ 1 ……p1 ^a1)* (p2^0 + p2^1……p2^a2)……(pk^0 + pk^1+pk^2……pk^ak)
typedef long long LL;
unordered_map<int, int> heap;
break_down(n);
LL sum = 1;
for(auto h : heap)
{
int x = h.first, y = h.second;
LL res = 1;
while(y --)
{
res = res * x + 1;
}
sum *= res;
}
求最大公约数
求a和b的最大公约数x,可转化为求b和a%b的最大公约数,一直转化到一个数为0,则另外那个数就是最大公约数(0和任何一个数的最大公约数就是那个数本身)。
int gcd(int a, int b)
{
if(b == 0) return a;
else return gcd(b, a%b);
}
证明:x可整除a和b,而a%b == a - (a/b)*b => a%b == a - c* b , 所以a/x - c * b / x 也可除的尽,所以a和b的约数,就是b和a%b的约数,最大公约数也一样
-
筛法
埃氏筛法(筛质数)求出1-n所有质数的个数,对于每一个质数将其在1-n中的所有倍数去除,剩下的就是1-n所有质数
int get_prime(int n)
{
vector<int> heap;
for(int i = 2; i <= n; i ++)
{
if(st[i]) continue;
heap.push_back(i);
for(int j = i + i; i <= n; i ++)
st[j] = true;
}
return heap.size();
}
埃氏筛法有一些数被多个质数所遍历标记,导致时间偏长,通过线性筛法可将所有被标记的数只被遍历一次减少重复,线性筛法被标记的数应该只被他的最小质因子给遍历
int parms[N];
bool st[N];
int get_primes(int n)
{
int res = 0;
for(int i = 2; i <= n; i ++)
{
if(!st[i]) parms[res ++] = i;
for(int j = 0; parms[j] <= n / i; j ++)
{
st[parms[j] * i] =true;
if(i % parms[j] == 0) break;
}
}
return res;
}
将所有质数放入parms数组内,第二层for循环遍历所有质数(标记质数的倍数),parms[j] * i一定是合数故进行标记。
if( i % parms[j] == 0) break; 有两种情况
一种是i是parms[j]本身,那次是parms[j]为存储质数数组的最后一个质数,所以直接break即可。
第二种i是parms[j]的倍数,如果不退出循环的话,那么parms[j + 1] * i 就会被标记,但是parm[j + 1] * i 也是parms[j]的倍数,所以将会在后续的i种又遍历一次,这样就不能保证每个合数是被他最小的质因子给遍历标记的,就不能保证线性时间复杂度,所以要break;