算法基础课—数学知识(二)欧拉函数、欧拉定理、费马定理、快速幂、扩展欧几里得算法、逆元
欧拉函数
定义:
互质是公约数只有1的两个整数,叫做互质整数。
公式计算
定理的证明——用容斥原理算出来
思路:1-N中与N互质的个数——先对N进行分解质因数,可以用筛选法,去掉其所有质因数的倍数,剩下的就是互质的数
对于筛法——容斥原理(先筛去所有素数的倍数,同时会发现可能两个素数倍数相同,却被筛去了,所以要诚信加回来,于是就有了公式的第二行。同时又发现,如果做加法的话,中间三个都有的交叉区域会被多次叠加,所以要减去他们多加的重叠区域,然后以此类推,可以得到如上的公式,又会发现其是总公式的展开形式,于是证明了定理)。
题目
给定 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
求欧拉函数——模板
#include <iostream>
using namespace std;
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;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
cin >> x;
cout << phi(x) << endl;
}
return 0;
}
求欧拉函数——自己的代码
#include<iostream>
using namespace std;
int main(){
int n, i, j, x;
cin>>n;
for(i = 0; i < n; i ++){
cin>>x;
long long res = x;//可能res较大,所以要用long long
for(j = 2; j <= x / j; j ++){
if(x % j == 0){
res *= 1 - 1.0 / j;//注意要是1.0/j,否则得到的不会是分数
while(x % j == 0) x = x / j;
}
}
if(x > 1)
res = res * (1 - 1.0 / x);
cout<<res<<endl;
}
}
线性筛法求每个数的欧拉函数
关键:抓住pji和i之间的欧拉函数的关系。如果是i是素数,则其欧拉函数为i-1.
我们之前用线性筛法用线性的时间就可以把所有不是素数的数筛除,在这个基础上,我们也可以把每个数的欧拉函数顺便求一遍。
我们可以发现欧拉函数就是要求的其实是他们的分解质因数,不需要知道他们的幂次,而在线性筛法中,我们是z抓住了pji和i之间的关系,然后对他们的pji的数进行操作,于是我们是否也可以推导φ(pji)和φ(i)的关系呢?于是我们就有了下面的情况:
第一种情况i mod pj == 0, 说明pj是i的最小质因数,于是φ(pj * i)= pj * φ (i)
第二种情况 i mod pj != 0 , 说明pj不是i的质因数,于是φ(pj * i)= pj * φ(i)*(1-1/pj)
于是我们便可以用线性筛法来求解每个数的欧拉函数
题目
给定一个正整数 n,求 1∼n 中每个数的欧拉函数之和。
输入格式
共一行,包含一个整数 n。
输出格式
共一行,包含一个整数,表示 1∼n 中每个数的欧拉函数之和。
数据范围
1≤n≤106
输入样例:
6
输出样例:
12
筛法求欧拉函数——模板
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1000010;
int primes[N], cnt;
int euler[N];
bool st[N];
void get_eulers(int n)
{
euler[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i])
{
primes[cnt ++ ] = i;
euler[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)
{
euler[t] = euler[i] * primes[j];
break;
}
euler[t] = euler[i] * (primes[j] - 1);
}
}
}
int main()
{
int n;
cin >> n;
get_eulers(n);
LL res = 0;
for (int i = 1; i <= n; i ++ ) res += euler[i];
cout << res << endl;
return 0;
}
筛法求欧拉函数——自己的代码
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int primes[N],cnt = 0;
long long euler[N];
bool st[N];
int main(){
int n, j, i;
cin>>n;
long long res = 1;//注意i==1的时候其欧拉函数==1
for(i = 2; i <= n; i ++){
if(!st[i]){
primes[cnt ++] = i;
euler[i] = i - 1;//注意定义如果是素数的情况
}
for(j = 0; primes[j] <= n / i; j ++){
st[primes[j] * i] = true;
if(i % primes[j] == 0){
euler[primes[j] * i] = euler[i] * primes[j];
break;
}
else{
euler[primes[j] * i] = euler[i] * primes[j] * (1 - 1.0 / primes[j]);
}
}
res += euler[i];
}
cout<<res<<endl;
}
欧拉函数的应用——欧拉定理
如果a和n互质,则a的φ(n)次方 mod n 同余1
证明
若a与n互质,
假设在1-n中有a1…aφ(n)的互质数,将他们相乘得到M = a1…aφ(n)
如果将这φ(n)个互质数都分别乘以a,再同时相乘得到M,会发现他们mod n的结果都相同,于是
得到第三行公式,于是推出第四行公式
欧拉定理的特例——费马定理
当n是质数p时
φ(p) = p - 1
所以a的p-1次方mod p 等于1
快速幂
快速的求出a的k次方mod p 的结果
时间复杂度logk
原先计算a的k次方需要k次操作,我们发现可以把k化成logk个数相加,不禁联想到二进制的形式
a的k次方 可以等于 a 的 2的x1次方+a的2的x2次方
2^x1 + 2 ^ x2 +…+ 2 ^xt = k (二进制)
于是我们只需要分别去计算a的2 ^ xk 次方是多少,并依次把他们加起来
同时a的2 ^ xk = (a的2 ^ (xk - 1))^ 2,所以也方便叠加求解
快速幂的思想,相当于以每次2的幂次的速度到达a的k次方。会比不断叠加1要来的快
题目
给定 n 组 ai,bi,pi,对于每组数据,求出 abiimodpi 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含三个整数 ai,bi,pi。
输出格式
对于每组数据,输出一个结果,表示 abiimodpi 的值。
每个结果占一行。
数据范围
1≤n≤100000,
1≤ai,bi,pi≤2×109
输入样例:
2
3 2 5
4 3 9
输出样例:
4
1
模板
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL qmi(int a, int b, int p)
{
LL res = 1 % p;
while (b)
{
if (b & 1) res = res * a % p;
a = a * (LL)a % p;
b >>= 1;
}
return res;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, b, p;
scanf("%d%d%d", &a, &b, &p);
printf("%lld\n", qmi(a, b, p));
}
return 0;
}
自己的代码
#include <iostream>
using namespace std;
typedef long long LL;
int main(){
int b, n, p;
int i;
LL a;
cin>>n;
for(i = 0; i < n; i ++){
cin>>a>>b>>p;
LL res = 1 % p;
a = a % p;
while(b){
if(b & 1)//取最后一位
res = res * a % p;
a = a * a % p;//这个要写在if判断外面,不断是否有都要继续乘
b = b >> 1;//注意不能只写b>>1 还要赋值
}
cout<<res<<endl;
}
}
快速幂求逆元
关键:只有当b和x互质,x为质数的时候才可以用。
把复杂的除法转变成乘法,x就是b的逆元
可以发现其本身与逆元之间的关系是
b * b的逆元 在mod n 会等于 1
可以发现b乘b的逆元mod p等于1
于是我们联想到费马定理(p一定是质数)
所以b的p-2次方就是b的逆元,于是可以用快速幂来求解
同时要注意,只有b和p互质且p是质数,才可以用费马定理,才可以以此求出逆元
题目
给定 n 组 ai,pi,其中 pi 是质数,求 ai 模 pi 的乘法逆元,若逆元不存在则输出 impossible。
注意:请返回在 0∼p−1 之间的逆元。
乘法逆元的定义
若整数 b,m 互质,并且对于任意的整数 a,如果满足 b|a,则存在一个整数 x,使得 a/b≡a×x(modm),则称 x 为 b 的模 m 乘法逆元,记为 b−1(modm)。
b 存在乘法逆元的充要条件是 b 与模数 m 互质。当模数 m 为质数时,bm−2 即为 b 的乘法逆元。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个数组 ai,pi,数据保证 pi 是质数。
输出格式
输出共 n 行,每组数据输出一个结果,每个结果占一行。
若 ai 模 pi 的乘法逆元存在,则输出一个整数,表示逆元,否则输出 impossible。
数据范围
1≤n≤105,
1≤ai,pi≤2∗109
输入样例:
3
4 3
8 5
6 3
输出样例:
1
2
impossible
模板
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL qmi(int a, int b, int p)
{
LL res = 1;
while (b)
{
if (b & 1) res = res * a % p;
a = a * (LL)a % p;
b >>= 1;
}
return res;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, p;
scanf("%d%d", &a, &p);
if (a % p == 0) puts("impossible");
else printf("%lld\n", qmi(a, p - 2, p));
}
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/50238/
来源:AcWing
自己的代码
#include<iostream>
using namespace std;
typedef long long LL;
int main(){
int n, i, b, p;
cin>>n;
for(i = 0; i < n; i++){
cin>>b>>p;
if(b % p == 0) cout<<"impossible"<<endl;
else{
LL a = b % p, res = 1;
int t = p - 2;
while(t){
if(t & 1) res = res * a % p;
a = a * a % p;
t = t >> 1;
}
cout<<res<<endl;
}
}
}
扩展欧几里得算法
裴蜀定理
对于一个正整数a,b ,一定存在整数x,y,使得ax + by = a和b的最大公约数,即a和b能凑出来的最小的正整数
证明
a是最大公约数的倍数,b是最大公约数的倍数,所以ax+by也是最大公约数的倍数,所以d是最大公约数的倍数,所以一定存在x,y ,ax+by= 最大公约数,其是ax+by能凑出的最小正整数
求解过程
算法本质上是要去求x0和y0
ax+by = (a,b),又(a,b) = (b, a mod b) ,
于是根据(b,a mod b)又可以推导出 by + (a mod b)x = (b,a mod b)。
进一步推导等于by + (a - [a/b] * b)*x = (b,a mod b)。
整理一下会等于ax + b*( y - [a/b] * x) = d。于是可以更新上一步的x和y。
相当于在求最大公约数的过程中,a,b一直在往下走,
所以x,y一直也在往下走,知道a % b == 0 ,则返回最大公约数a,
然后反过来迭代回来更新x和y,直到更新到最后一步的时候,才是真正的x,y。
主要核心思想:(a,b) = (b, a mod b)。可以一直这么递归下去,知道a mod b = 0 ,则返回a,同时这个时候x,y也是确定的。
又利用 (a,b)= ax + by 然后进一步递归到 b,a mod b 时 x,y 发生了更新,于是反过来更新x,y。
求出一个解可以求出多个通解,可以发现可能存在多个x,y
题目
给定 n 对正整数 ai,bi,对于每对数,求出一组 xi,yi,使其满足 ai×xi+bi×yi=gcd(ai,bi)。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含两个整数 ai,bi。
输出格式
输出共 n 行,对于每组 ai,bi,求出一组满足条件的 xi,yi,每组结果占一行。
本题答案不唯一,输出任意满足条件的 xi,yi 均可。
数据范围
1≤n≤105,
1≤ai,bi≤2×109
输入样例:
2
4 6
8 18
输出样例:
-1 1
-2 1
模板
#include <iostream>
#include <algorithm>
using namespace std;
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;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, b;
scanf("%d%d", &a, &b);
int x, y;
exgcd(a, b, x, y);
printf("%d %d\n", x, y);
}
return 0;
}
代码
#include <iostream>
using namespace std;
int gcb(int a, int b, int &x, int &y){
if(!b){
x = 1;
y = 0;
return a;
}
else{
int s = gcb(b, a % b, y, x);
y = y - a/b * x;
return s;
}
}
int main(){
int n, x, y, i, a, b;
cin>>n;
for(i = 0; i < n; i++){
cin>>a>>b;
gcb(a, b, x, y);
cout<<x<<" "<<y<<endl;
}
}
线性同余方程
a和x的乘积除以m的余数是b,要求这个x
题目的等价变形
如果b是a,m最大公约数的倍数,则有解,否则,无解
如果有解,则按照扩展欧几里得算法来求即可。
中国剩余定理
****这一块留待补充
题目
给定 n 组数据 ai,bi,mi,对于每组数求出一个 xi,使其满足 ai×xi≡bi(modmi),如果无解则输出 impossible。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组数据 ai,bi,mi。
输出格式
输出共 n 行,每组数据输出一个整数表示一个满足条件的 xi,如果无解则输出 impossible。
每组数据结果占一行,结果可能不唯一,输出任意一个满足条件的结果均可。
输出答案必须在 int 范围之内。
数据范围
1≤n≤105,
1≤ai,bi,mi≤2×109
输入样例:
2
2 3 6
4 3 5
输出样例:
impossible
-3
模板
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
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;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, b, m;
scanf("%d%d%d", &a, &b, &m);
int x, y;
int d = exgcd(a, m, x, y);
if (b % d) puts("impossible");
else printf("%d\n", (LL)b / d * x % m);
}
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/50248/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
代码
#include <iostream>
using namespace std;
int gcb(int a, int b, int &x, int &y){
if(!b){
x = 1;
y = 0;
return a;
}
else{
int s = gcb(b, a % b, y, x);
y = y - a / b * x;
return s;
}
}
int main(){
int i, n, a, b, m, x, y;
cin>>n;
for(i = 0; i < n; i ++){
cin>>a>>b>>m;
int s = gcb(a, m, x, y);
if(b % s == 0) cout<<x<<endl;
else cout<<"impossible"<<endl;
}
}
逆元
这一段部分摘自https://blog.csdn.net/wcxyky/article/details/103366577
前提
满足求余的运算
(a + b) % p = (a%p + b%p) %p (对)
(a - b) % p = (a%p - b%p) %p (对)
(a * b) % p = (a%p * b%p) %p (对)
(a / b) % p = (a%p / b%p) %p (错)
除法不满足求余取模运算
逆元的定义
a*x = 1 (mod p) 其中x叫做a关于p的逆元
比如2 * 3 % 5 = 1,(即2*3=1(mod 5))那么3就是2关于5的逆元,或者说2和3关于5互为逆元
a的逆元,我们用inv(a)来表示
那么(a / b) % p = (a * inv(b) ) % p = (a % p * inv(b) % p) % p
逆元:
a和p互质,a才有关于p的逆元
求逆元方法1——快速幂、费马定理——前提:其关于的数是质数p,且与a互质
求逆元方法1:
由费马小定理 a^(p-1) ≡1 (mod p)
两边同除以a得:a^(p-2) ≡ inv(a) (mod p)
所以inv(a) = a^(p-2) (mod p)
这个用快速幂可以求,复杂度O(logn)
求逆元方法2——扩展欧几里得定理
求逆元
a * x = 1 (mod m)
方法:相当于这里的b 为1,去求x ,实现方法和扩展欧几里得类似。