文章目录
欧拉函数
#include <iostream>
using namespace std;
#include<algorithm>
int test_01()
{
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); //最后一个 ,因为是从i = 2开始判断质数 ,但公式到N
}
return 0;
}
874. 筛法求欧拉函数
const int N = 1000010;
typedef long long LL;
int primes[N],cnt; //质因子数组 ,统计质因子个数
int phi[N]; //欧拉函数
bool st[N]; //标记数组
//线性筛法 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] 的最小质因数了,故循环结束。
*/
LL get_eulers(int n)
{
phi[1] = 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++) //筛
{
st[primes[j] * i] = true; //被筛
if(i % primes[j] == 0) // break; //去除 ?
{
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
LL res = 0;
for(int i = 1 ;i <= n ; i++) res += phi[i];
return res;
}
int test_02()
{
int n;
cin >> n;
cout << get_eulers(n) << endl;
return 0;
}
875. 快速幂 O(logk)
#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; //也可以写成 k%2 【 if语句在k奇数即 k%2 == 1时执行 】
k >>= 1; //k /= 2;
a = (LL)a * a % p;
}
return res;
}
int test_03()
{
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. 快速幂求逆元
逆元:
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 test_04()
{
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. 扩展欧几里得算法
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 test_05()
{
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;
}
int test_06()
{
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;
}
int main() {
test_06();
return 0;
}