欧拉函数
题目描述 :
给定 n 个正整数 ai,请你求出每个数的欧拉函数。
欧拉函数的定义
1~N 中与 N 互质的数的个数被称为欧拉函数,记为 ?(N)。
若在算数基本定理中,N=p1a1 * p2a2… pmam,则:
φ(N) = N×(p1-1)/p1×(p2-2)/p2×…×(pm-1)/pm
输入输出格式 :
输入
第一行包含整数 n。
接下来 n 行,每行包含一个正整数 ai。
输出
输出共 n 行,每行输出一个正整数 ai 的欧拉函数。
输入输出样例 :
输入
3
3
6
8
输出
2
2
4
定义:
给定n个正整数,请你求出每个数的欧拉函数
1~N中与N互质的数的个数为欧拉函数值φ(N)
N = p1a1 * p2*a2 * p3a3 … * pnan; 【先转换成分解质因数形式】
φ(N) = N * (1 - 1 / p1) * (1 - 1 / p2) * … * (1 - 1 / pn) 【与质因子的次数没有关系】
【证明:利用容斥原理结论】
1.从1~N中去掉p1 , p2 , … pk 的所有倍数 【会被去两次,如既是p1的倍数又是p2的倍数,所以要加回来】
2.加上所有pi * pj 的倍数 [i与j为1~k中任意两个数]
3.减去所有pi * pj * pk ;
4.加上所有pi * pj * pk * pl ;
1~N中所有与N互质的个数刚好为容斥原理公式: 减1加2减3加4…减N-1加N
N - N/p1 - N/p2- N/p3- N/p4 … - N / pn 为倍数个数
加 N / (p1p2) + … + N / (p(n-1) * pn )
减N / (p1p2p3) …
加 N / (p1p2p3*p4)
== N * (1 - 1 / p1) * (1 - 1 / p2) * … * (1 - 1 / pn) == φ(N)
简记:欧拉函数φ(N) == N * (1 - 1 / p1) * (1 - 1 / p2) * ... * (1 - 1 / pn)
== N / pi *(p1-1)
N × Π ( p i − 1 p i ) N× \Pi (\dfrac{pi-1}{pi}) N×Π(pipi−1)
#include <iostream>
using namespace std;
#include<algorithm>
int main()
{
int n;
cin >> n;
while(n --)
{
int a;
cin >> a;
int res = a;
for(int i = 2;i <= a / i;i ++) //求质因数
if(a % i == 0)
{
res = res / i * (i - 1); //欧拉函数公式
while(a % i == 0) a /= i; //除去质因数的指数次 ,去除此质因数倍数
}
if(a > 1) res = res / a * (a - 1); //可能有> sqrt(a) 的质因数
}
return 0;
}
874. 线性筛法求欧拉函数
给定一个正整数n,求1~n中每个数的欧拉函数之和。
输入格式
共一行,包含一个整数n。
输出格式
共一行,包含一个整数,表示1~n中每个数的欧拉函数之和。
数据范围
1≤n≤106
输入样例:
6
输出样例:
12
分类讨论:
- i%pj == 0 φ(pj * i) == pj * φ(i) == i * ∑(1 - 1 / p[n])
- i%pj != 0 φ(pj * i) == pj * φ(i) * (1 - 1 / pj) == φ(i) * (pj - 1)
φ ( p j ∗ N ) = p j ∗ φ ( N ) ∗ ( p j − 1 p i ) 约分 = φ ( N ) ∗ ( P j − 1 ) φ(pj * N) = pj * φ(N) *(\dfrac{pj-1}{pi}) \dfrac{约分}{} = φ(N) * (Pj-1) φ(pj∗N)=pj∗φ(N)∗(pipj−1)约分=φ(N)∗(Pj−1)
//线性筛法 O(n) 【 能求很多... ,如下面就在 if(i % primes[j] == 0)语句变型分类判断 ,在求质因子的过程中顺带求出欧拉函数值
/*
prime[cnt++]=i;:如果发现i是素数,那么就把它放进prime数组里,同时把计数器加一。
prime[j]<=n/i;如果pj乘以i的值大于n就判断完了,【选择成对约数小的 n/a | n - - -> a < n^1/2^】。
内层for循环要做两件事情,分为两个条件判断:
1.如果i除以prime[j](简称pj)不等于0,说明pj不是i的最小质因子,并且可以说明pj比i的最小质因子小。
这样,pji的最小值因子就一定是pj,就先把pji这个数筛掉。
2.如果i除以pj等于0,说明pj是i的最小质因子,而我们如果继续把j++的话,那么下一个pj就一定不是当前i的最小质因子了,为了避免后面重复计算造成错误,所以这里要break。
综上所述,无论pj能否整除i,pj*i一定是合数,一定要筛掉。【约数成对出现】
线性筛法相当于是对埃氏筛法求素数个数的一种优化,线性筛是用最小质因数进行素数筛选,而埃氏筛法则是通过每一个数的倍数进行素数筛选。比如6这个数,用线性筛法只会被筛一次,而用埃氏筛法会分别被2和3各筛一次。
这里引用别的思路2~~:
用线性筛求质数个数的具体做法为 :
在void getPrimes(int n)函数中,最外层依旧是从2遍历到n进行循环,st[i]表示数字i是否被筛除。
每一次循环开始时,若是if(!st[i]), 则primes[cnt ++ ] = i,表明i是个素数。
不管i是否为素数,我们都要再在for (int j = 0; primes[j] <= n / i ; j ++ )这个循环中,对其他数进行筛选。
先让st[primes[j] * i ]= true, 在此可以保证prime[j] 一定是prime[j] * i的最小质因数,
因为后面还有一行代码,if(i % primes[j] = = 0) break :
i % primes[j] == 0 表明primes[j]是i的最小质因数,
如果i % primes[j] != 0 ,prime[j]也一定小于i 的最小质因数,因为primes[j]是从大到小存储素数的。
至于这里为什么要进行break,因为当primes[j]成为i的最小质因数之后,
在下一次循环得到的primes[j + 1] 就不再是i * primes[j + 1] 的最小质因数了,故循环结束。
#include<bits/stdc++.h>
using namespace std;
const int N = 1000010;
typedef long long LL;
int primes[N],cnt; //质因子数组 ,统计质因子个数
int phi[N]; //欧拉函数表
bool st[N]; //标记数组
LL get_eulers(int n)
{
phi[1] = 1;//定义出发,1与自己互质
for(int i = 2; i <= n;i++) //线性筛法 + phi代表欧拉函数符号
{
if(!st[i])
{
primes[cnt ++] = i;
phi[i] = i - 1; //为质数,欧拉函数就为i-1
}
for(int j = 0; primes[j] <= n / i;j++)
{
st[primes[j] * i] = true; //筛非质数
if(i % primes[j] == 0) // 倍数遍历完 break;
{
phi[primes[j] * i] = phi[i] * primes[j]; //==0记不用减1
break;
}
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
LL res = 0; //1~n的欧拉函数之和
for(int i = 1 ;i <= n ; i++) res += phi[i];
return res;
}
int main()
{
int n;
cin >> n;
cout << get_eulers(n) << endl;
return 0;
}
欧拉函数用途:欧拉定理
欧拉定理: aφ(n) 与 n 互质 则 gcd(aφ(n) , n) = 1 或者 说 aφ(n) 与 n mod 1 同余
【学数学要完全自己想,能推出一遍公式,在背了很多熟练的基础上】
费马小定理:
ap-1 与 p 与1同模 == gcd(ap-1,p) = 1
875. 快速幂 O(logk)
给定 n 组 ai,bi,pi,对于每组数据,求出 aibi mod pi 的值。[ mod p 答案就在 1 ~ p -1 之中]
输入格式
第一行包含整数 n。
接下来 n 行,每行包含三个整数 ai,bi,pi。
输出格式
对于每组数据,输出一个结果,表示 aibi mod pi 的值。
每个结果占一行。
数据范围
1≤n≤100000,
1≤ai,bi,pi ≤2 ×109
输入样例:
2
3 2 5
4 3 9
输出样例:
4
1
快速幂 O(logk) 【反复平方法】
ak mod p
1 <= a,k,p <= 1e9
预处理: a20^^ mod p , a21^^ mod p ,a22^^ mod p ,
ak = ∑aij^^ == a2x0+2x1+…+2xn^^ ;
即把ak的拆成 a平方k次
简记:typedef long long LL;
LL qmi(底数,幂,%p)
while(k)
{
if(k & 1)res = (LL)res * a % p; //奇数次幂开始多乘1个a
k >>= 1; 等效 k /= 2; 【 >>= 1 等效 /= 2 】
a = (LL)a * a % p;
}
#include<algorithm>
typedef long long LL;//数论大多题long long ,读入数据也用scanf较好
LL qmi(int a,int k,int p)
{
int res = 1;
while(k) //2^x^ (奇数+ 1) == k
{ //若指数为奇数,多乘一个a
if(k & 1)res = (LL)res * a % p; //奇数次幂开始多乘1个a [也可以写成 k%2]【 if语句在k奇数即 k%2 == 1时执行 】
k >>= 1; //k /= 2;
a = (LL)a * a % p;
}
return res;
}
int main()
{
int n;
scanf("%d",&n);
while(n --)
{
int a,k,p;
scanf("%d%d%d",&a,&k,&p);
printf("%lld\n",qmi(a,k,p));
}
return 0;
}
876. 快速幂求逆元
给定n组ai,pi,其中pi是质数,求ai模pi的乘法逆元,若逆元不存在则输出impossible。
注意:请返回在0~ p - 1 之间的逆元。
乘法逆元的定义
若整数b,m互质,并且b|a,则存在一个整数x,使得a/b≡ax(mod m),则称x为b的模m乘法逆元,记为b(-1) (mod m)。
b存在乘法逆元的充要条件是b与模数m互质。当模数m为质数时,b^(m?2)即为b的乘法逆元。
输入格式
第一行包含整数n。
接下来n行,每行包含一个数组ai,pi,数据保证pi是质数。
输出格式
输出共n行,每组数据输出一个结果,每个结果占一行。
若ai模pi的乘法逆元存在,则输出一个整数,表示逆元,否则输出impossible。
数据范围
1≤n≤105,
1≤ai,pi≤2109
输入样例:
3
4 3
8 5
6 3
输出样例:
1
2
impossible
逆元:
a / b ≡ a * x (mod m)
a / b ≡ a * b逆元 (mod m)
两边同乘b ,a ≡ a * b * b逆元 (mod m)
消去a , b * b逆元 ≡ 1 (mod m) 【性质】
再由费马定理 : bp-1 ≡ 1 (mod p) - - - > 重要结论:b * b p-2 ≡ 1 (mod p) 【证明完毕】
【找到一个x,使得b*x 同余 1 (mod p) ,则可取 x = bp-2】
费马小定理: 如果p是一个质数,而整数a不是p的倍数,则有a(p-1)≡1(mod p) 因此a mod p的逆元是 a(p-2)
求取a(p-2) 可以用快速幂求取
逆元能解决类似:(a / b mod p)类型的问题
简记:qmi(底数,次幂,mod p) 快速幂
if(a % p)printf("%lld",res); else puts("impossible");
因为 a % p == 0时会为res = 1,不正确,应该为impossible ,所以用 a % p
typedef long long LL;
#include<cstdio>
LL qmi(int a,int k,int p)
{
LL res = 1; //初始值为1 !!!
while(k)
{
if(k & 1) res = (LL)res * a % p; //强制转换
k >>= 1;
a = (LL)a * a % p;
}
return res;
}
int main()
{
int n;
scanf("%d",&n);
while(n --)
{
int a,k,p;
scanf("%d%d",&a,&p); //已知推导公式: a逆元求法: a * a^p - 2 ^ ≡ 1 (mod p) 即k = p - 2
LL res = qmi(a,p-2,p);//a逆元 == a^p-2^ % p
if(a % p)printf("%lld",res); //因为 a % p == 0时会为res = 1,不正确,应该为impossible ,所以用 a % p
else puts("impossible");
}
return 0;
}
877. 扩展欧几里得算法
(递归,裴蜀定理,gcd)
给定 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
拓展欧几里得算法
时间复杂度:O(n*log(a+b))
①拓展欧几里得算法解决的问题:对于任意给定的两个正整数a、b,求解x,y使得ax+by=(a,b) (a,b)的意思:a和b的最大公约数
②问题引入:裴蜀定理
给定任意一对正整数a、b,存在非零整数x、y,使得ax+by=(a,b)
扩欧作用:求解方程 ax+by=gcd(a,b) 的解(x、y)、求逆元 等
推理:
①当 b=0 时 ax+by=a 所以 x=1,y=0
②当 b≠0 时
设gcd(a,b)=gcd(b,a%b)=d
又
by′+(a%b)x′=gcd(b,a%b)
by′+(a–a/b–b)*x’=gcd(b,a%b)
b(y’-[a/b]x’)+ax’=gcd(b,a%b)=gcd(a,b)=d
所以
x=x′(不变),y=y’ - [a/b]x’(更新)
要用scanf,这时候连cin解除同步都比它慢得多
//本题需要用子问题的结果来计算当前问题的结果,所以需要等子问题算完之后再算
裴蜀定理:
对任意一对正整数 a,b,那么一定存在非零整数x,y【构造法证明】,使得ax+by = gcd(a,b) 即a,b能凑出来的最小的正整数
构造法证明:即欧几里得算法:
通解
x = x0 - (b/a) * k , k∈Z
y = y0 + (b/a) * k , k∈Z
int gcd(int a,int b)
{
if(b == 0)return a; //0和任何数的最大公约数都是那个数本身
return gcd(b,a%b);
}
int exgcd(int a,int b,int &x,int &y) //扩展欧几里得算法
{
if(!b) //若b = 0 ,返回 x = 1,y = 0
{
x = 1,y = 0;
return a;
}
int d = exgcd(b,a%b,y,x); //把a,b颠倒了,x,y对应系数翻转
y -= a / b * x;
return d;
}
int main()
{
int n;
scanf("%d",&n);
while(n --)
{
int a,b,x,y;//根据题意,让x,y引用传递 exgcd( int a,int b , int &x,int &y);
scanf("%d%d",&a,&b);
exgcd(a,b,x,y);
printf("%d %d\n",x,y);
}
return 0;
}
应用:求线性同余方程 ax 与 b 同余 (mod m)
给定n组数据ai,bi,mi,对于每组数求出一个xi,使其满足ai?xi≡bi(mod mi),如果无解则输出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
exgcd求解线性同余方程
如果 a?x≡b(mod m)
那么存在 y∈Z 使得 a?x+m?y=b
令d=gcd(a,m)
exgcd(a,m,x0,y0) 得a?x0+m?y0=d
if(b % d == 0)则有解
等式两边同乘 b/d 得
b/d(a?x0+m?y0)=b
a?(b/d?x0)+m?(b/d?y0)=b
x=b/d?x0
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"); //d不是b的倍数,无解
else printf("%d\n",(LL)x * (b / d) % m);
}
return 0;
}