数论
质数
1,试除法判定质数(O(根号N))
题目:试除法判定质数
给定 n 个正整数 ai,判定每个数是否是质数。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个正整数 ai。
输出格式
共 n 行,其中第 i 行输出第 i 个正整数 ai 是否为质数,是则输出 Yes,否则输出 No。
数据范围
1≤n≤100,
1≤ai≤231−1
输入样例:
2
2
6
输出样例:
Yes
No
这道题很简单,就不过多介绍了,但是强调一点
时间复杂度是根号N
#include<iostream>
using namespace std;
int n, a;
bool Check_Prime(int a)
{
if(a==1)
return false;
for (int i = 2; i <= a / i; i++)
{
if (a % i == 0) return false;
}
return true;
}
int main()
{
cin >> n;
while (n--)
{
cin >> a;
if (Check_Prime(a))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
return 0;
}
强调上述代码中的 for (int i = 2; i <= a / i; i++)
之所以可以这样写,因为除1之外,因数都是成对出现的,如果有一个因数自然而然就有另外一个因数,所以可以这样写,但是最好不要i*i<=a
因为这样有可能会溢出爆掉int
2,分解质因数(O(根号N))
原题链接:分解质因数
给定 n 个正整数 ai,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个正整数 ai。
输出格式
对于每个正整数 ai,按照从小到大的顺序输出其分解质因数后,每个质因数的底数和指数,每个底数和指数占一行。
每个正整数的质因数全部输出完毕后,输出一个空行。
数据范围
1≤n≤100,
2≤ai≤2×109
输入样例:
2
6
8
输出样例:
2 1
3 1
2 3
什么是质因数
质因数就是它的因子是质数,根据算术基本定理,不考虑排列顺序的情况下,每个正整数都能够以唯一的方式表示成它的质因数的乘积。
n=p1^a1 * p2^a2 *p3a3…pnan
同时还有一个性质,如果一个数字最多只有一个大于sqrt(n)的质因子。
反证法:如果有两个,那么二者相乘便大于n,所以不合理,因此最多只有一个大于sqrt(n)的质因子,所以当除到最后n的值还大于1的话,那么就只剩下唯一的那个大于当时的sqrt(n)。
为什么说大于当时的sqrt(n),因为n的值随着我们求质因子的时候是会改变的,
#include<iostream>
using namespace std;
int n, a;
void divide(int a)
{
for (int i = 2; i <= a / i; i++)
{
//如果a能够整除的话,那么i此时一定是质数
//如果i是合数的话,那么i一定可以化成多个质因子相乘的形式
//这质因子同时也是a的质因子,而a的质因子在之前已经被除尽了
//所以i一定不是合数,i是一个质数.
if (a % i == 0)
{
int s = 0;
while (a % i == 0)s++,a /= i;//除尽相应的质因子
//a的值是一直变化的
cout << i << " " << s << endl;
}
}
if (a > 1)
cout << a << " " << "1" << endl;
cout << endl;
}
int main()
{
cin >> n;
while (n--)
{
cin >> a;
divide(a);
}
return 0;
}
如果理解不了可以拿一个例子自己模拟一下。
例如:180 = 2^2*3^2*5
;
我们模拟上面的数字,当i = 5的时候此时a变成了5,那么这个时候会直接跳出循环,判断这个时候的a是否大于1就可以了。
3,筛质数
原题链接:筛质数
给定一个正整数 n,请你求出 1∼n 中质数的个数。
输入格式
共一行,包含整数 n。
输出格式
共一行,包含一个整数,表示 1∼n 中质数的个数。
数据范围
1≤n≤106
输入样例:
8
输出样例:
4
1,朴素做法:O(nlogn)
#define _CRT_SECURE_NO_WARNINGS 1
#include<cstring>
#include<iostream>
using namespace std;
const int N = 10000010;
int primes[N];//用来存放质数
int cnt, n;
bool st[N];//判断是否为质数
void Get_prime2(int n)
{
for (int i = 2; i <= n; i++)
{
if (!st[i])primes[cnt++] = i;//先把质数存起来
for (int j = i; j <= n; j += i)
st[j] = true;//不管是质数还是合数,将它后面的倍数筛掉
}
}
int main()
{
cin >> n;
memset(st, false, sizeof st);
Get_prime2(n);
cout << cnt << endl;
return 0;
}
原理
时间复杂度推导
诶氏筛法 O(nloglogn)
void get_prime3(int n)
{
for (int i = 2; i <= n; i++)
{
if (!st[i])
{
primes[cnt++] = i;//先把质数存起来
for (int j = i; j <= n; j += i)
st[j] = true;//这里是只筛掉质数的倍数,因为每一个合数都可以化成其前面之前质数的倍数
}
}
}
原理
其实诶氏筛法和之前的朴素筛法原理都差不多,我们可以发现之前朴素筛法的时候每次都是用质数取筛,这是因为任何合数都可以等于小于它的那个质数的倍数。
时间复杂度分析
线性筛法 O(n)(原理在代码中)
==线性筛法和上面的诶氏筛法最大的不同就是,线性筛法每个数字它只会筛一次,每个数字都是拿它的最小质因数筛掉的。所以时间复杂度就是O(N)了
原理在代码的注释里面就有,如果不理解可以拿一个具体的数字理解康康
关键:
理解每个合数都是拿它的最小质因数晒掉的
例如2至10
4 = 2*2 是用2筛掉的
6 = 2*3 是用我筛掉的
8 = 2*4 也是用2
9 = 3*3 3筛掉的
这样就保证了每个合数只会被筛选一次,但是诶氏筛法 有些数字多次筛选了
#include<cstring>
#include<iostream>
using namespace std;
const int N = 10000010;
int primes[N];//用来存放质数
int cnt, n;
bool st[N];//判断是否为质数
void Get_prime1(int n)
{
for (int i = 2; i <= n; i++)
{
//如果是st[i] = false说明是质数
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] <= n / i; j++)
//prime[]数组中的素数是递增的,当i能整除prime[j],那么i*prime[j+1]这个合数
//肯定被prime[j]乘以某个数筛掉。
//因为i中含有prime[j],prime[j]比prime[j+1]小,
//即i=k*prime[j],那么i*prime[j+1]=(k*prime[j])*prime[j+1]=k’*prime[j],
//接下去的素数同理。所以不用筛下去了。
//因此,在满足i%prime[j]==0这个条件之前以及第一次满足改条件时,
//prime[j]必定是prime[j]*i的最小因子。
{
st[primes[j] * i] = true;//此时就是用最小质因子来筛数
if (i % primes[j] == 0)//如果没有这句话,那么下一个循环的时候primes[j+1]*i
//化成primes[j+1]*prime[j]*k = x =primes[j]
//此时的primes[j+1]并不是最小质因子,不符合线性筛法的原理了;
break;
}
}
}
int main()
{
cin >> n;
memset(st, false, sizeof st);
Get_prime1(n);
cout << cnt << endl;
return 0;
}
约数
1,约数个数(O(根号N))
原理都在代码的注释里
原题链接:约数个数
给定 n 个正整数 ai,请你输出这些数的乘积的约数个数,答案对 109+7 取模。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个整数 ai。
输出格式
输出一个整数,表示所给正整数的乘积的约数个数,答案需对 109+7 取模。
数据范围
1≤n≤100,
1≤ai≤2×109
输入样例:
3
2
6
8
输出样例:
12
//算法核心:
// 首先定义上,n可以分解质因数:n = p1^a1*p2~a2*p3~a3...pk~ak(底数都是质数)
// 由约数的定义可知:p1^a1的约数有p1^0,p1^1,p1^2....p1^a1一共(a+1)个
// 根据乘法定理则n的约数总个数为(a1+1)*(a2+1)*(a3+1).....*(ak+1);
//
//
//
#include<iostream>
#include<unordered_map>
using namespace std;
const int Mod = 1e9 + 7;
int n, a;
int main()
{
cin >> n;
unordered_map<int, int> res;//哈希表,前者表示的是底数,后者表示的是指数
while (n--)
{
cin >> a;
for (int i = 2; i <= a / i; i++)
{
while (a % i == 0)
{
a /= i;
res[i]++;
}
}
if (a > 1)//a的最大公约数可能大于sqrt(a);
res[a]++;
}
long long sum = 1;
for (auto p : res)
{
sum = (sum * (p.second + 1)) % Mod;
}
printf("%lld\n",sum);
return 0;
}
约数之和(O(根号N))
原题链接:约数之和
给定 n 个正整数 ai,请你输出这些数的乘积的约数个数,答案对 109+7 取模。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个整数 ai。
输出格式
输出一个整数,表示所给正整数的乘积的约数个数,答案需对 109+7 取模。
数据范围
1≤n≤100,
1≤ai≤2×109
输入样例:
3
2
6
8
输出样例:
12
理解:
while (y--) t = (t * x + 1) % Mod;
t = t*p+1;
t = 1;
t = p+1;
然后
t = p^2+p+1;
t = p^3+p^2+p+1;
......
t = p^a+p^a-1+....+p+1;
首先定义上,n可以分解质因数:n = p1^a1*p2^a2*p3^a3...pk^ak(底数都是质数)
由约数的定义可知:p1^a1的约数有p1^0,p1^1,p1^2....p1^a1一共(a+1)个
根据乘法定理则n的约数总个数为(a1+1)*(a2+1)*(a3+1).....*(ak+1);
//p1^a1的约数之和是p1^0,p1^1,p1^2....p1^a1一共(a1+1)个
//p2^a2的约数之和是p2^0,p2^1,p2^2....p2^a2一共(a2+1)个
//同理pk^ak的约数之和是pk^0,pk^1,pk^2....pk^ak一共(ak+1)个
//则可以化成(p1^0+p1^1+p1^2+...p1^a1)*(p2^0+p2^1+p2^2+...p2^a2)*.....(pk^0+pk^1+pk^2+...pk^ak)
//直接乘开就可以发现其实每一项都是约数,相加起来刚好就是约数之和
//辅助理解:相加之和有很多()+()+()+()+....+()
//其中从p1^a1约数里面选有(a1+1)种选法p2^a2约数里面选有(a2+1)种选法pk^ak约数里面选有(ak+1)种选法
//相乘发现刚好符合上一题中的约数个数之和实在是理解不了也可以直接模拟一个数字,或者直接记住结论。
#include<iostream>
#include<unordered_map>
using namespace std;
const int Mod = 1e9 + 7;
int n, a;
int main()
{
cin >> n;
unordered_map<int, int> res;//哈希表,前者表示的是底数,后者表示的是指数
while (n--)
{
cin >> a;
for (int i = 2; i <= a / i; i++)
{
while (a % i == 0)
{
a /= i;
res[i]++;
}
}
if (a > 1)//a的最大公约数可能大于sqrt(a);
res[a]++;
}
long long sum = 1;
for (auto p : res)
{
long long x = p.first, y = p.second;
long long t = 1;
while (y--) t = (t * x + 1) % Mod;
sum = sum * t % Mod;
}
cout << sum << endl;
return 0;
}
最大公约数(O(log n))
原理链接:最大公约数
一个字简单
给定 n 对正整数 ai,bi,请你求出每对数的最大公约数。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个整数对 ai,bi。
输出格式
输出共 n 行,每行输出一个整数对的最大公约数。
数据范围
1≤n≤105,
1≤ai,bi≤2×109
输入样例:
2
3 6
4 6
输出样例:
3
2
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
//欧几里得算法
int Get_divisor(int a, int b)
{
return b ? Get_divisor(b, a % b) : a;//如果b不是0那么返回第一个值,当b是0的时候返回a
}
int main()
{
int n, a, b;
cin >> n;
while (n--)
{
cin >> a >> b;
int t = Get_divisor(a, b);
cout << t << endl;
}
return 0;
}
欧几里得算法的简单证明:
欧拉函数
1,什么是欧拉函数
在数论中,欧拉函数f(n)被定义为:小于等于n的正整数中和n互质的数的数目(互质即两者最大公约数为1)
如f(1)=1,因为与小于等于1的数中与1互质的数只有1
再如f(8)=4,因为小于等于8中的数与8互质的数有1,3,5,7,而2与8最大公约数为2,4与8最大公约数为4,6与8最大公约数为2
欧拉函数的性质(含证明)
字丑但是证明没错哈哈哈
1,欧拉函数(oj)
原题链接:欧拉函数
给定 n 个正整数 ai,请你求出每个数的欧拉函数。
欧拉函数的定义
1∼N 中与 N 互质的数的个数被称为欧拉函数,记为 ϕ(N)。
若在算数基本定理中,N=pa11pa22…pamm,则:
ϕ(N) = N×p1−1p1×p2−1p2×…×pm−1pm
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个正整数 ai。
输出格式
输出共 n 行,每行输出一个正整数 ai 的欧拉函数。
数据范围
1≤n≤100,
1≤ai≤2×109
输入样例:
3
3
6
8
输出样例:
2
2
4
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
long long n, a;
int main()
{
cin >> n;
while (n--)
{
cin >> a;
long long res = a;
for (int i = 2; i <= a / i; i++)
{
if (a % i == 0)
{
res = res / i * (i - 1);//不能写成res*(i-1)/i,因为如果这样的话可能会溢出
while (a % i == 0)
{
a /= i;
}
}
}
if(a>1)
res = res/a*(a-1);
cout << res << endl;
}
return 0;
}
2,筛法求欧拉函数(基于线性筛法)
原题链接:筛法求欧拉函数
给定一个正整数 n,求 1∼n 中每个数的欧拉函数之和。
输入格式
共一行,包含一个整数 n。
输出格式
共一行,包含一个整数,表示 1∼n 中每个数的欧拉函数之和。
数据范围
1≤n≤106
输入样例:
6
输出样例:
12
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1000010;
bool st[N];//判断数字是否为质数
int phi[N], prime[N];//前者存的是欧拉数后者存的是质数
int cnt;//质数的序号
int n;
long long Get_euler(int n)
{
phi[1] = 1;
long long res = 0;
for (int i = 2; i <= n; i++)
{
if (!st[i])
{
prime[cnt++] = i;
phi[i] = i - 1;//质数的欧拉数个数等于其值减一
}
for (int j = 0; prime[j] <= n / i; j++)
{
int t = prime[j] * i;
st[t] = true;
if (i%prime[j] == 0)
{
phi[t] = prime[j] * phi[i];
//此时prime[j]是i的质因子,那么t的质因子和i的质因子相同
//phi[t] = prime[j]*i*(1-1/p1)*(1-1/p2)*....*(1-1/pk);
//而i*(1-1/p1)*(1-1/p2)*....*(1-1/pk) = phi[i];
//所以:phi[t] = prime[j] * phi[i];
break;
}
//虽然此时i%prime[j]!=0;
//但是prime[j]一定是prime[j]*i的质因子并且prime[j]*i的质因子就是比i的质因子多了一个prime
//那么phi[prime[j]*i] = prime[j]*i*(1-1/p1)*(1-1/p2)*....*(1-1/pk)*(1-1/prime[j]);
//i*(1-1/p1)*(1-1/p2)*....*(1-1/pk) = phi[i]
//prime[j]*(1-1/prime[j]) = phi[prime[j] = prime[j]-1(因为prime[j]是质数,符合基本性质);
phi[t] = phi[i] * (prime[j] - 1);
}
}
for (int i = 1; i <= n; i++) res += phi[i];
return res;
}
int main()
{
cin >> n;
memset(st,false,sizeof st);
cout << Get_euler(n) << endl;
return 0;
}