ACM Weekly 4(待修改)

ACM Weekly 4

  • 涉及的知识点
    • GCD与LCM
      • GCD和LCM
      • 质因数分解与互质
      • 拓展欧几里得算法
      • 拓展欧几里得应用
    • 算数基本定理及其推论
      • 算数基本定理
      • 推论1:求约数个数
      • 推论2:求约数之和
    • 欧拉函数
    • 同余
    • 费马小定理
    • 欧拉定理
    • 乘法逆元
    • 难题解析
    • 拓展
      • ICPC线上测试赛
      • 中国剩余定理
      • 大数小数定理
      • Pollard Rho算法

涉及的知识点

第四周练习主要涉及GCD与LCM(欧几里得、质因数分解、互质的概念)、算数基本定理及其推论、,欧拉函数、同余、费马小定理、欧拉定理、扩展欧几里得算法、乘法逆元

拓展:ICPC线上测试赛、中国剩余定理、大数小数定理、Pollard Rho算法

GCD与LCM

GCD和LCM

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还有一些常用的性质:

  1. a、b都可以分成有限个质数的乘积
  2. gcd(a,b)中只包含了a、b中全部公共质数因子
  3. lcm(a,b)中包含了a、b中全部质数因子
  4. gcd%lcm=0
  5. gcd* lcm=a*b
  6. lcm/gcd=a/gcd*b/gcd
  7. gcd(ka,kb)=k*gcd(a,b)//对lcm同理
  8. lcm(a/b,c/d)=lcm(a,c)/gcd(b,d)
  9. gcd(lcm(a,b),lcm(a,c))=lcm(a,gcd(b,c))

用集合的思想来理解的话,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;

题目

ACM Weekly 4(待修改)_第1张图片
题目大意:给出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;
}

题目

ACM Weekly 4(待修改)_第2张图片

题目大意:给定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;
    }
}

题目

ACM Weekly 4(待修改)_第3张图片

题目大意:给出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;
}

题目

ACM Weekly 4(待修改)_第4张图片
题目大意:求出一个数列的公共除数的个数

思路:一开始是想用试除法将这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. 求解不定方程

  2. 求解模线性方程

  3. 求模的逆元

算数基本定理及其推论

算数基本定理

对于任意一个大于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α2Pnαn,这里 P 1 < P 2 < ⋯ < P n P_1P1<P2<<Pn且均为质数, a i a_i ai为正整数。这样的分解称为N的标准分解式

试除法求一个数所有约数

代码

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());
}

推论1:求约数个数

由基本定理可得,对于一个非质数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β2Pnβ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;
}

推论2:求约数之和

对于一个数N= P 1 α 1 P 2 α 2 … P n α n P^{\alpha_1}_1P^{\alpha_2}_2\dots P^{\alpha_n}_n P1α1P2α2Pnα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β2Pnβ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;
}

题目
题目大意:
思路:
代码


题目

题目大意:

思路:

代码


题目

题目大意:
思路:
代码


欧拉函数

同余

费马小定理

欧拉定理

乘法逆元

难题解析

题目

题目大意:
思路:


代码

题目

题目大意:

思路:

代码

题目

题目大意:

思路
代码


拓展

ICPC线上测试赛

中国剩余定理

大数小数定理

Pollard Rho算法

题目

题目大意:
思路:
代码

你可能感兴趣的:(ACM训练)