友友们好(^-^)🌹🌹🌹,我是杨枝,一枚在算法领域迈步的尽心博主,目前还是一只纯纯的菜汪🐶。 典型的又菜又爱闹那种👀,做不好很多事,说不好很多话,写题还总不Ac😅,还在努力还在前进👣。 你们对我来说都是是独一无二的💓。 时刻谨记:认真写算法,用心去分享。不负算法,不误卿。 感谢相遇(^㉨^)
🔔由理论推导到代码实践逐步精准掌握数论 (二)
💓欧拉函数
🌟概念引入
1. 互质: 如果两个数的最大公约数是1,则称这两个数互质。 |
2. 欧拉函数: 在1~N中与N互质的数的个数被称为欧拉函数,记作:φ(N) 假如对这个正整数按照算术基本定理分解,就可按照下图的流程,推导出欧拉函数的计算公式 |
看起来就脑袋痛,对吧。但是顺着我圈的地方去读,会感觉,呀,就这呀,就这呀,豁然开朗喔~ |
💓公式法求欧拉函数
🌟算法模板
实现流程图:
算法的代码描述如下:
公式法求欧拉函数
int phi(int x)
{
int res = x;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
res = res / i * (i - 1);
while (x % i == 0) x /= i;
}
if (x > 1) res = res / x * (x - 1);
return res;
}
🌟例题描述
🎇🎇🎇原题传送门🎇🎇🎇
🌟参考代码(C++版本)
🎇🎇🎇点击这里查看参看代码喔QWQ🎇🎇🎇
🌟疑难点剖析
一、分解质因数是基础
对分解质因数的算法模板要清楚,假如对分解质因数比较模糊的小伙伴可以看看这篇文章喔,也是挂过热榜的,值得信赖呀 |
🎇🎇🎇由理论推导到代码实践逐步精准掌握数论 (一)🎇🎇🎇
二、欧拉函数的计算公式
记得住最好,假如记不住了,可以利用容斥原理,自己推理两三个数据就可以发现规律喔 |
💓线性筛法求欧拉函数
公式法求欧拉函数适用于只求少量数据的欧拉值,倘若题目要求很多数据的欧拉值或者欧拉值的乘积、和。那么公式法就可能TLE,因此,借助线性筛法的模板来优化求公式法求欧拉值的过程。 |
🌟算法模板
算法实现流程图:
算法模板代码实现:
筛法求欧拉函数
int primes[N], cnt; // primes[]存储所有素数
int phi[N]; // 存储每个数的欧拉函数
bool st[N]; // st[x]存储x是否被筛掉
void get_eulers(int n)
{
euler[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i])
{
primes[cnt ++ ] = i;
phi[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j ++ )
{
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0)
{
phi[t] = phi[i] * primes[j];
break;
}
phi[t] = phi[i] * (primes[j] - 1);
}
}
}
🌟例题描述
🎇🎇🎇原题传送门🎇🎇🎇
🌟参考代码(C++版本)
🎇🎇🎇点击这里查看参看代码喔QWQ🎇🎇🎇
🌟疑难点剖析
一、线性筛法的难点在于如何结合公式统计出当前参数的欧拉值,将相应结果存放到phi数组中。
情况一:当前这个数据是质数。 与质数n互质的数的个数有n-1个。比如质数7。1~6与它的最大公约数都是1,互质的个数就是6。因此可以得到phi[n] = n-1; |
情况二:筛选中,当前这个质数primes[j]是i的质因数 phi[primes[j] * i] 可变型为phi[primes[j] ] * phi[i] 。因为primes[j]是i的质因数,所以在用公式算phi[i]的时候就已经把(1-1/primes[j])算了。所以对于phi[primes[j]]就只用将剩下的primes[j]乘上。 因此 phi[primes[j] * i] = phi[i]*primes[j]; |
情况三:筛选中,当前这个质数primes[j]不是i的质因数。 因为primes[j]不是质因数了,就得独立的对它进行公式计算,即phi[ primes[j] ] = primes[j] x (1 - 1/primes[j]) x phi[i] 因此,结果为: phi[primes[j] * i] = phi[i]*(primes[j]-1); |
二、注意范围,因为题目所给的数据就能很大,统计欧拉值的时候以乘积的形式统计的,可能会造成溢出。对于数论的题,可以泛泛而谈大多数时候都是要用long long 类型的,因为所给的数据一般比较大,比如2 x 10^9。假如再进行乘法运算,就会溢出
💓快速幂
快速幂也叫欧拉降幂、反复平方法。是数论中常客的常客了。 使用背景:一般看到幂运算的时候就可以考虑把快速幂拿出来了。 |
快速幂的时间复杂度是O(logn),举个栗子,假如要处理109的数据,大概只需要运算30次,比O(n2)的效率高了很多个档次了。
快速幂算法的原理说简单一点,就是把幂二进制化,比如对于x22
22的二进制是(10110)2
那么对于x22按照快速幂的原理的拆分法,拆出来就是下面的效果:
x22 = x16 * x4 * x2
基本了解的算法怎么实现的,那么下面咱们具体从例题中落实吧
🌟算法模板
幂运算实现流程:
幂运算代码实现:
快速幂
求 m^k mod p,时间复杂度 O(logk)。
int qmi(int m, int k, int p)
{
int res = 1, t = m;
while (k)
{
if (k&1) res = res * t % p;
t = t * t % p;
k >>= 1;
}
return res;
}
🌟例题描述
🎇🎇🎇原题传送门🎇🎇🎇
🌟参考代码
🎇🎇🎇点击这里查看参看代码喔QWQ🎇🎇🎇
🌟疑难点剖析
小心int溢出
因为快速幂中是指数函数彼此之间的乘积运算。当数据十分庞大的时候,int可能装不下,所以要强转为long long类型if(k&1) res = (LL)res*a%p; k>>=1; a=(LL)a*a%p;
💓拓展欧几里得算法
🌟前提引入
拓展欧几里得算法是在欧几里得算法的基础上,证明裴蜀定理而产生的的。 |
裴蜀定理:
对于任意整数a,b,存在一对整数x,y,满足 ax + by = gcb(a,b)
下图是李煜东老师书中对定理证明的详细步骤。后续算法的代码落实也是依赖于这个证明中进行公式变换的部分 |
🌟算法模板
算法实现流程
算法代码描述:
扩展欧几里得算法
// 求x, y,使得ax + by = gcd(a, b)
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1; y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= (a/b) * x;
return d;
🌟例题描述
🎇🎇🎇原题传送门🎇🎇🎇
🌟参考代码(C++版本)
🎇🎇🎇点击这里查看参看代码喔QWQ🎇🎇🎇
🌟疑难点剖析
注意要拓展欧几里得算法实现的时候,在函数参数中,要传入系数x和y的引用