第四周练习主要涉及GCD与LCM(欧几里得、质因数分解、互质的概念)、算数基本定理及其推论、,欧拉函数、同余、费马小定理、欧拉定理、扩展欧几里得算法、乘法逆元
拓展:ICPC线上测试赛、中国剩余定理、大数小数定理、Pollard Rho算法
GCD为最大公约数,LCM为最小公倍数
两者的核心是:
g c d ( a , b ) = g c d ( b , a % b ) gcd(a,b)=gcd(b,a\%b) gcd(a,b)=gcd(b,a%b)
l c m ( a , b ) = a / g c d ( a , b ) ∗ b lcm(a,b)=a/gcd(a,b)*b lcm(a,b)=a/gcd(a,b)∗b
(这样写为防溢出)
GCD与LCM还有一些常用的性质:
用集合的思想来理解的话,gcd为a、b两数因子的交集,lcm为a、b两数因子的并集因此9可证,当然,这样的证明只是抽象的理解,下面为标准证明
g c d ( l c m ( a , b ) , l c m ( a , c ) ) gcd(lcm(a,b),lcm(a,c)) gcd(lcm(a,b),lcm(a,c))
= g c d ( a b g c d ( a , b ) , a c g c d ( a , c ) ) =gcd(\frac{ab}{gcd(a,b)},\frac{ac}{gcd(a,c)}) =gcd(gcd(a,b)ab,gcd(a,c)ac)
= a × g c d ( b g c d ( a , b ) , c g c d ( a , c ) ) =a×gcd(\frac{b}{gcd(a,b)},\frac{c}{gcd(a,c)}) =a×gcd(gcd(a,b)b,gcd(a,c)c)
= a × g c d ( b , c ) g c d ( a , b , c ) =a×\frac{gcd(b,c)}{gcd(a,b,c)} =a×gcd(a,b,c)gcd(b,c)
= l c m ( a , g c d ( b , c ) ) =lcm(a,gcd(b,c)) =lcm(a,gcd(b,c))
辗转相除法(欧几里得算法)实现
代码
int GCD(int a,int b)
{
int res=a>b?a:b;
while(res)
{
res=a%b;
a=b;
b=res;
}
return a;
题目
题目大意:给出t个例子,每个例子给出一个整数n,从1~n中取两个数,求他们的最大公约数,之后找出所有最大公约数的最大值
思路:最大值即为n/2,如果n为奇数,那么最大值为gcd(n-1,(n-1)/2),反之,为gcd(n,n/2)
代码
#include
using namespace std;
int t,n;
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
printf("%d\n",n>>1);
}
return 0;
}
题目
题目大意:给定n个数,计算它们之间两两组合的最大公倍数序列的最小公因数
思路:使用性质9
代码
#include
#include
using namespace std;
typedef long long ll;
ll data[100001],GCD[100001],res;
int n;
ll gcd(ll a,ll b)
{
return b?gcd(b,a%b):a;
}
ll lcm(ll a,ll b)
{
return a/gcd(a,b)*b;
}
int main()
{
cin >>n;
for(int i=1;i<=n;i++)//输入
cin >>data[i];
for(int i=n;i>=1;i--)
GCD[i]=gcd(data[i],GCD[i+1]);//求出除去当前元素后其他元素的公共最大公约数
for(int i=1;i<=n;i++)
res=gcd(res,lcm(GCD[i+1],data[i]));//运用性质9获得总的最大公约数,递推思想,求出每个序列后缀的最大公约数
cout <<res;
return 0;
}
质因数定义:如果一个质数是某个数的因数,那么这个质数为此数的质因数。
互质定义:gcd(a,b)=1
代码(具体解释详见算数基本定理)
int p[N];
int c[N];
int cnt;
void divide(int n)
{
for(int i = 2; i <= sqrt(n); ++i)
if(n % i == 0)
{
p[++cnt] = i;
c[cnt] = 0;
while(n % i == 0)
{
n /= i;
++c[cnt];
}
}
if(n > 1)
{
p[++cnt] = n, c[cnt] = 1;
}
}
题目
题目大意:给出t个正整数,对每个整数而言,至少有一个整数对a,b使得两者的最大公约数与最小公倍数之和等于该整数,如果只有一对整数对,输出a,b,否则输出符合条件的任意一对
思路:通过判断奇偶来执行对应的输出
代码
#include
#include
using namespace std;
long long x=0,t=0;
int main()
{
cin >>t;
while(t--)
{
cin >>x;
if(x==2)
cout<<"1 1"<<endl;
else
{
if(x^1)//如果是偶数,则必定存在整数对1、x-1满足条件
cout<<"1 "<<x-1<<endl;
else//如果是奇数同理
cout<<"2 "<<x-2<<endl;
}
}
return 0;
}
题目
思路:一开始是想用试除法将这n个数字的因数及其指数存下来然后遍历去判等,但发现这样不行,一个是时间为 n + n n n+n\sqrt n n+nn,另一个是每个数字在某一质数位上分的指数不一定就是1,后来学习学长的代码,该题解法为,求出整个序列的最大公约数,再看这个公约数有多少因数。
代码
#include
#include
#include
using namespace std;
typedef long long ll;
ll n,input[500000],GCD,ans,acc[500000],res=1;
void divide()
{
for(int i=2; i<=GCD/i; ++i)
{
if(GCD%i==0)
{
acc[++ans]=0;
while(GCD%i==0)
{
GCD/=i;
++acc[ans];
}
}
}
if(GCD>1)
acc[++ans]=1;
}
int main()
{
cin >>n;
for(int i=0; i<n; ++i)
cin >>input[i];
GCD=input[0];
for(int i=1; i<n; ++i)
GCD=gcd(GCD,input[i]);
divide();
for(int i=1;i<=ans;i++)//算数基本定理算约数个数
res*=acc[i]+1;
cout <<res<<endl;
return 0;
}
对于不完全为 0 的非负整数 a,b,gcd(a,b)表示 a,b 的最大公约数,则必然存在整数对 x,y ,使得 gcd(a,b)=ax+by成立
证明:
令a>b
b=0时,gcd(a,b)=a,存在x=1,y=0
a*b!=0时,
设ax1+by1=gcd(a,b)
又bx2+(a%b)y2=gcd(b,a%b)
由gcd(a,b)=gcd(b,a%b)
可以推出
ax1+by1=bx2+(a-a/b*b)y2
ax1+by1=ay2+bx2-a/b*by2
因为该式恒等,可得x1=y2,y1=x2-(a/b)*y2
x1、y1基于x2、y2(bx2+(a%b)y2=gcd(b,a%b))得出
之后进行递归/迭代可以最终求出x1、y1,最后总能到达b=0
代码(待理解)
int exgcd(int a, int b, int &x, int &y)
{
if(b == 0)
{
x = 1,y = 0;
return a;//这一层的x
}
int d = exgcd(b, a % b, x, y);
int z = x;
x = y;//这一层的x
y = z - y * (a / b);//这一层的y
return d;
}
求解不定方程
求解模线性方程
求模的逆元
对于任意一个大于1的自然数N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积N= P 1 α 1 P 2 α 2 … P n α n P^{\alpha_1}_1P^{\alpha_2}_2\dots P^{\alpha_n}_n P1α1P2α2…Pnαn,这里 P 1 < P 2 < ⋯ < P n P_1
试除法求一个数所有约数
代码
vector<int> res;
void GetDivisors(int x)
{
for (int i=1;i<=x/i;i++)//i<=x/i等价于i*i<=x
if(x%i==0)//如果能整除
{
res.push_back(i);//如果x可以整除i,将i加入
if (i!=x/i) //录入另一个因数
res.push_back(x/i);
}
sort(res.begin(),res.end());
}
由基本定理可得,对于一个非质数N,其约数D必可表示为D= P 1 β 1 P 2 β 2 … P n β n ( 0 ≤ β i ≤ α i ) P^{\beta_1}_1P^{\beta_2}_2\dots P^{\beta_n}_n(0\le\beta^i\le\alpha^i) P1β1P2β2…Pnβn(0≤βi≤αi)
那么,求约数个数的问题便可以转换成:
β 1 − β n \beta_1-\beta_n β1−βn间每一个 β \beta β取法数累积,所以总个数为
( α 1 + 1 ) ( α 2 + 1 ) ( α 3 + 1 ) … ( α n + 1 ) (\alpha_1+1)(\alpha_2+1)(\alpha_3+1)\dots(\alpha_n+1) (α1+1)(α2+1)(α3+1)…(αn+1)
题目
题目大意:给定N个数,求出这N个数的乘积的约数个数对 1 0 9 + 7 10^9+7 109+7的模
思路:由推论1可得,我们将这N个数分别进行质因数分解,统计各底数的数量,最后进行累积即可
代码
#include
#include
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
int main()
{
int N;
cin>>N;
unordered_map<int, int>primes;//用unordered_map存储,提高速度
while(N--)
{
int x;
cin>>x;
for (int i=2; i<=x/i; i++)
while(x%i==0)//如果可以整除
{
x/=i;//去掉这一个底数
primes[i]++;//底数的指数加1
}
if(x>1)primes[x]++;//若x大于1,说明x为先前未曾记录的质因数底数,记录该底数
}
ll res = 1;
for (auto p:primes) res=res*(p.second+1)%mod;//累积
cout<<res<<endl;
return 0;
}
对于一个数N= P 1 α 1 P 2 α 2 … P n α n P^{\alpha_1}_1P^{\alpha_2}_2\dots P^{\alpha_n}_n P1α1P2α2…Pnαn,其一个约数D= P 1 β 1 P 2 β 2 … P n β n ( 0 ≤ β i ≤ α i ) P^{\beta_1}_1P^{\beta_2}_2\dots P^{\beta_n}_n(0\le\beta^i\le\alpha^i) P1β1P2β2…Pnβn(0≤βi≤αi)
便可得到约数之和(用排列组合的思维去证明): ( P 1 0 + P 1 1 + ⋯ + P 1 α 1 ) ( P 2 0 + P 2 1 + ⋯ + P 2 α 2 ) … ( P n 0 + P n 1 + ⋯ + P n α n ) (P^{0}_1+P^{1}_1+\dots +P^{\alpha_1}_1)(P^{0}_2+P^{1}_2+\dots +P^{\alpha_2}_2)\dots(P^{0}_n+P^{1}_n+\dots +P^{\alpha_n}_n) (P10+P11+⋯+P1α1)(P20+P21+⋯+P2α2)…(Pn0+Pn1+⋯+Pnαn)
题目
题目大意:给定N个数,求出这N个数的和的约数个数对 1 0 9 + 7 10^9+7 109+7的模
思路:由推论2,只需求出 P i 0 + P i 1 + ⋯ + P i α 1 P^{0}_i+P^{1}_i+\dots +P^{\alpha_1}_i Pi0+Pi1+⋯+Piα1并累积即可,令t=1,每次t=p*t+1,第一次执行后t=p^2+p+1,第a次后为所求式子
代码
#include
#include
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
int main()
{
int N;
cin>>N;
unordered_map<int, int>primes;//用unordered_map存储,提高速度
while(N--)
{
int x;
cin>>x;
for (int i=2; i<=x/i; i++)
while(x%i==0)//如果可以整除
{
x/=i;//去掉这一个底数
primes[i]++;//底数的指数加1
}
if(x>1)primes[x]++;//若x大于1,说明x为先前未曾记录的质因数底数,记录该底数
}
ll res = 1;
for (auto p:primes)
{
ll a = p.first, b = p.second; //a为底数,b为该底数数量,即指数
ll t = 1;
while (b--) t=(t*a+1)%mod;
res=res*t%mod;
}
return 0;
}
题目
题目大意:
思路:
代码
题目
题目大意:
思路:
代码
题目
题目大意:
思路:
代码
题目
题目大意:
思路:
代码
题目
题目大意:
思路:
代码
题目
题目大意:
思路
代码
题目
题目大意:
思路:
代码