我是目录
数论2
写在前面
前面更新了数论1(质数+约束),这次我继续更新数论2(欧拉函数+快速幂)
欧拉函数
欧拉函数
下面请看代码:
#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 euler(int a)
{
int res=a;
for(int i=2;i<=a/i;i++)
{
if(a%i==0)
{
while(a%i==0)
a/=i;
res = res / i*(i-1);
}
}
if(a>1) res = res /a*(a-1);
return res;
}
int main()
{
int n;
long long res=0;
cin>>n;
for(int i=1;i<=n;i++)
{
res+=euler(i);
}
cout<<res;
return 0;
}
这样写的时间复杂的为O(n∗√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);
}
}
}
最后的总代码:
#include<iostream>
using namespace std;
const int N = 1e6+10;
bool st[N];
int primes[N],idx,euler[N];
int main()
{
int n;
euler[1]=1;//与1互质的数就只有1
long long res = 0;
cin>>n;
for(int i=2;i<=n;i++)
{
if(!st[i])//未被筛过,i为质数
//对于一个质数n,其euler[n]=n-1,因为若n为质数,那么2~n-1中没有一个数是n的约数,那么1~n中与n互质的数就有1~n-1这n-1个数
//∴euler[n]=n-1;
euler[i] = i-1;
primes[idx++] = i;
for(int j=0;primes[j]<=n/i;j++)
{
st[primes[j]*i] = true;
/*
求euler[primes[j]*i],分析euler[primes[j]*i]和euler[i]的关系
如果i%primes[j]==0时,说明primes[j]为i的最小质因子,而euler[i]内已经乘过(primes[j]-1)/primes[j]
∴计算euler[primes[j]*i]只需要更新N的值即可,即在euler[i]的基础上乘个primes[j]
∴euler[primes[j]*i]=primes[j]*euler[i];
如果i%primes[j]!=0时,primes[j]小于i的最小质因子,而euler[i]内没有乘过(primes[j]-1)/primes[j]
∴计算euler[primes[j]*i]除了需要更新N值外,还需要乘上(primes[j]-1)/primes[j]
∴euler[primes[j]*i]=primes[j]*euler[i]*(primes[j]-1)/primes[j]=euler[i]*(primes[j]-1)
*/
if(i % primes[j] == 0){
euler[primes[j]*i]=primes[j]*euler[i];
break;
}
else euler[primes[j]*i]=euler[i]*(primes[j]-1);
}
}
for(int i=1;i<=n;i++) res+=euler[i];
cout<<res;
return 0;
}
线性筛法求欧拉数时间复杂度为 O(n)
快速幂
快速幂
暴力O(n∗b)
基本思路:对于n组数据,分别循环b次求出ab mod p
#include<iostream>
using namespace std;
int main()
{
int n;
cin>>n;
while(n--)
{
int a,b,p;
long long res=1;
cin>>a>>b>>p;
while(b--)
res = res * a %p;
cout<<res<<endl;
}
}
快速幂解法O(n∗logb)
基本思路:
反复平方法
任何一个数都可以被二进制表示
b&1就是判断b的二进制表示中第0位上的数是否为1,若为1,b&1=true,反之b&1=false
b&1也可以用来判断奇数和偶数,b&1=true时为奇数,反之b&1=false时为偶数
#include<iostream>
using namespace std;
long long qmi(long long a,int b,int p)
{
long long res=1;
while(b)//对b进行二进制化,从低位到高位
{
//如果b的二进制表示的第0位为1,则乘上当前的a
if(b&1) res = res *a %p;
//b右移一位
b>>=1;
//更新a,a依次为a^{2^0},a^{2^1},a^{2^2},....,a^{2^logb}
a=a*a%p;
}
return res;
}
int main()
{
int n;
cin>>n;
while(n--)
{
cin.tie(0);
ios::sync_with_stdio(false);
int a,b,p;
long long res=1;
cin>>a>>b>>p;
res = qmi(a,b,p);
cout<<res<<endl;
}
return 0;
}
快速幂之递归版 O(n∗logb)
#include<iostream>
using namespace std;
#define ull unsigned long long
ull quick_pow(ull a,ull b,ull p)
{
if(b==0) return 1;
a%=p;
ull res=quick_pow(a,b>>1,p);
if(b&1) return res*res%p*a%p;
return res*res%p;
}
int main()
{
int n;
cin>>n;
while(n--)
{
int a,b,p;
cin.tie(0);
ios::sync_with_stdio(false);
cin>>a>>b>>p;
cout<<quick_pow(a,b,p)<<endl;
}
return 0;
}
快速幂求逆元
快速幂求逆元
直接把快速幂的代码CV一下就可以了。
#include <iostream>
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 = (LL)a * a % p;
b >>= 1;
}
return res;
}
int main()
{
int n; cin >> n;
while(n --){
int a, p;
cin >> a >> p;
if(a % p == 0) puts("impossible");
else cout << qmi(a, p - 2, p) << endl;
}
return 0;
}
扩展欧几里得算法求逆元
#include <iostream>
using namespace std;
typedef long long LL;
int n;
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()
{
cin >> n;
while (n --)
{
int a, p, x, y;
// if (a < p) swap(a, p);
cin >> a >> p;
int d = exgcd(a, p, x, y);
if (d == 1) cout << ((LL)x + p) % p << endl;//保证x是正数
else puts("impossible");
}
return 0;
}
扩展欧几里得算法
拓展欧几里得算法解决的问题:对于任意给定的两个正整数a,b,求解x,y使得ax+by=(a,b) //(a,b)的意思为,a和b的最大公约数
问题的引入:裴蜀定理
给定任意一对正整数a,b,存在非零整数x,y,使得ax+by=(a,b)
方法一:
#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<cstdio>
using namespace std;
void exgcd(int a,int b,int& x,int& y)
{
if(!b)
{
x=1,y=0;
return ;
}
exgcd(b,a%b,x,y);
int t = y;
y = x-a/b*y;
x = t;
}
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);
}
return 0;
}
中国剩余定理
表达整数的奇怪方式
关于这个定理百度百科有很长的一个解释,这里我就不多说了。中国剩余定理(孙子定理)链接
附上代码:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL exgcd(LL a, LL b, LL &x, LL &y)
{
if (!b)
{
x = 1, y = 0;
return a;
}
LL d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main()
{
int n;
cin >> n;
LL x = 0, m1, a1;
cin >> m1 >> a1;
for (int i = 0; i < n - 1; i ++ )
{
LL m2, a2;
cin >> m2 >> a2;
LL k1, k2;
LL d = exgcd(m1, m2, k1, k2);
if ((a2 - a1) % d)
{
x = -1;
break;
}
k1 *= (a2 - a1) / d;
k1 = (k1 % (m2/d) + m2/d) % (m2/d);
x = k1 * m1 + a1;
LL m = abs(m1 / d * m2);
a1 = k1 * m1 + a1;
m1 = m;
}
if (x != -1) x = (a1 % m1 + m1) % m1;
cout << x << endl;
return 0;
}