#include<iostream>
#include<algorithm>
using namespace std;
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);
//等价于res = res * (1 - 1 / i)
while(a % i == 0) a /= i;
}
}
if(a > 1)res = res / a * (a -1);
cout << res << endl;
}
}
利用筛法求欧拉函数
这个其实不太好推
cpp复制代码
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1000010;
int primes[N], cnt; // 存储所有质数
int phi[N]; // 存储每个数的欧拉函数值
bool st[N]; // 标记是否是合数,st[i]=true表示i是合数
LL get_eulers(int n){
phi[1] = 1; // 规定φ(1)=1
for(int i = 2; i <= n; i++){
if(!st[i]){ // 如果i是质数
primes[cnt ++] = i; // 存储质数
phi[i] = i - 1; // 性质1:质数p的φ(p)=p-1
}
// 用当前已知的质数去筛i的倍数
for(int j = 0; primes[j] <= n / i; j ++){
st[primes[j] * i] = true; // 标记合数
if(i % primes[j] == 0){ // 如果primes[j]是i的质因子
// 性质2:如果p整除n,则φ(p×n)=p×φ(n)
phi[primes[j] * i] = primes[j] * phi[i];
break; // 保证每个合数只被最小质因子筛
}
// 性质3:如果p不整除n,则φ(p×n)=(p-1)×φ(n)
phi[primes[j] * i] = (primes[j] - 1) * phi[i];
}
}
// 计算前缀和
LL res = 0;
for(int i = 1; i <= n; i ++) res += phi[i];
return res;
}
快速幂
先说一下快速幂是来处理什么问题的,他是用来快速求出 a ^ k mod P的结果,在o( log k)的时间复杂度求出结果
#include<iostream>
#include<algorithm>
using namespace std;
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);
//等价于res = res * (1 - 1 / i)
while(a % i == 0) a /= i;
}
}
if(a > 1)res = res / a * (a -1);
cout << res << endl;
}
}
快速幂求逆元
之前文章也讲过这个,具体可以看之前的文章,我们这里只给个求逆元的模板
cpp复制代码
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
int n;
// a^k %p
int qmi(int a, int k, int p){
int res = 1;
while(k){
if(k & 1) res = (LL)res * a % p;
k >>= 1;
a = (LL)a * a % p;
}
return res;
}
int main(){
scanf("%d", &n);
while(n --){
int a, p;
scanf("%d%d", &a, &p);
int res = qmi(a, p - 2, p);
if(a % p) printf("%d\n", res);
else puts("impossible");
}
}
裴蜀定理
有一对正整数a, b,那么存在整数x, 使得ax + by = gcd(a, b)
如果ax + by = d,那么d一定是gcd(a, b)的倍数,所以可以凑出最小的a,b的系数正好是的=最大公约数
扩展欧几里得
看数学推导
我们要解:a × x + b × y = gcd(a, b)
假设我们已经递归计算了下一层: b × x₁ + (a % b) × y₁ = gcd(b, a % b)
根据欧几里得算法:gcd(a, b) = gcd(b, a % b)
展开 a % b = a - ⌊a/b⌋ × b:
复制代码
b × x₁ + (a % b) × y₁
= b × x₁ + (a - ⌊a/b⌋ × b) × y₁
= a × y₁ + b × (x₁ - ⌊a/b⌋ × y₁)
比较一下
复制代码
x = y₁
y = x₁ - ⌊a/b⌋ × y₁
所以就得到了下面这个
cpp复制代码
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
cpp复制代码
#include<iostream>
using namespace std;
int exgcd(int a, int b, int &x, int &y){
if(b == 0){
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, x, y;
scanf("%d%d", &a, &b);
exgcd(a, b, x, y);
printf("%d %d\n", x, y);
}
}
线性同余方程
这个就是转化为扩展欧几里得的形式去求
cpp复制代码
#include<iostream>
using namespace std;
typedef long long LL;
int exgcd(int a, int b, int &x, int &y){
if(b == 0){
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)x * (b / d) % m);
}
}