再读《挑战程序设计竞赛》——初出茅庐(6)

简单数论问题,归根结底有这么几个:

  1. 质数问题(判断、筛法、分解、分解的几个常用结论)和约数问题
  2. 快速幂、矩阵快速幂
  3. gcd,exgcd(同余方程组问题) 欧拉函数
  4. 逆元
  5. 高等数学相关:高斯消元、组合数问题

其余更加复杂的数学问题在读高级篇的时候再说
讲解部分参考了《算法竞赛进阶指南》和acwing的课程。

质数的判定

试除法,只要除到根号n就可以了,因为两数相乘等于n,一定一个大于等于根号n,一个小于等于根号n。

bool is_prime(int x)
{
    if(x<2)
        return false; 
    for(int i=2;i<=sqrt(x);i++)
    {
        if(x%i==0)
            return false;
    }
    return true;
}

埃氏筛法

结果我都是推进一个vector再做后续处理。

bool primes(int x)
{
    for(int i=2;i<=x;i++)
    {
        if(v[i])
            continue;
        prime.push_back(i);
        for(int j=i;j<=x/i;j++)
            v[i*j]=1;
    }
}

如果是从[a,b)筛素数,质因数一定不超过sqrt(b)。在[2,sqrt(b))筛素数,把筛选的素数的倍数从[a,b)划掉即可。

质因数分解

任意一个大于1的数N都可以分解为:
在这里插入图片描述
leetcode周赛做过一道题,问不同质因子相乘能得到多少个数。就是c1+c2+…+cn
分解的结果我习惯放进一个pair数组中,再进行后续处理。

void divide(int x)
{
    for(int i=2;i<=sqrt(x);i++)
    {
        if(x%i==0)
        {
            int cnt=0;
            while(x%i==0)
            {
                x/=i;
                cnt++;
            }
            v.push_back({i,cnt});
        }
    }
    if(x>1)
    {
        v.push_back({x,1});
    }
}
}

注意不要漏掉x是素数的情况。

进阶指南中提到的几个质数和约数的公式:

N!中质因子p的个数为:
在这里插入图片描述
N中的每个约数都能表示为在这里插入图片描述
约数的个数:
在这里插入图片描述
所有正约数的和为:
在这里插入图片描述

快速幂

ll qmi(ll a,ll b,ll mod)
{
    ll ans=1;
    while(b)
    {
        if(b&1)
        {
            ans=a*ans%mod;
        }
        a=a*a%mod;
        
        b>>=1;
    }
    return ans;
}

矩阵快速幂

以一道最简单的数据较大的斐波那契说明:
f(n)=f(n-1)+f(n-2)
组合f(n)和f(n-1),写成矩阵形式为:
在这里插入图片描述
就可以通过这个简单矩阵的n次方递推了。

初始化的时候是一个单位矩阵,并且乘的时候用一个函数实现矩阵乘法(矩阵用一个记录维度和内容的结构体存储):

struct matrix {
   int a[100][100];
   int n;
};
//矩阵乘法的实现
matrix matrix_mul(matrix A, matrix B, int mod) {
    matrix ret;
    ret.n = A.n;
    //初始化结果矩阵
    for (int i = 0; i < ret.n; i++) {
        for (int j = 0; j < ret.n; j++){
            ret.a[i][j] = 0;
        }
    }
    //按照矩阵乘法的定义,i,j是正在计算的数在矩阵中的位置。由于矩阵乘法是很多个数相加,所以用k去枚举i行乘j列乘到了第几个数、
    for (int i = 0; i < ret.n; i++) {
        for (int j = 0; j < ret.n; j++) {
            for (int k = 0; k < A.n; k++) {
                ret.a[i][j] = (ret.a[i][j] + A.a[i][k] * B.a[k][j] % mod) % mod;
            }
        }
    }
    return ret;
}

//初始化单位矩阵。相当于快速幂里ans=1,矩阵快速幂中ans=unit
matrix unit(int n)
{
    matrix ret;
    ret.n=n;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            if(i==j)
            {
                ret.a[i][j]=1;
            }
            else
            {
                ret.a[i][j]=0;
            }
        }
    }
    return ret;
}
matrix matrix_pow(matrix A,int p,int mod)
{
    matrix res=unit(A.n);
    for(;p;p/=2)
    {
        if(p&1)
        {
            res=matrix_mul(res,A,mod);
        }
        A=matrix_mul(A,A,mod);
    }
    return res;
}

之后的题目,如果用矩阵快速幂的话公式会比较难推,要多加练习。

gcd

int gcd(int a,int b)
{
    if(b)
        return gcd(b,a%b);
    return a;
}

exgcd

不要纠结怎么来的,推了式子写法还要适应递归。

int exgcd(int a,int b,int &x,int &y)
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    int r=exgcd(b,a%b,x,y);
    int temp=y;
    y=x-(a/b)*y;
    x=temp;
    return r;
}

exgcd的运用场景:
求ax+by=c的解,给出a,b,c解出x, y
若c mod gcd(p,q)=0 则解为:之前算法算出的解 乘上c/gcd(p,q),否则没有整数解。

如果要求的是最小的整数解或者全部解
可以利用扩展欧几里得算法得出ax+by=gcd(a,b)的一个解(x1,y1):

然后求得ax+by=c的解为;

x=x1*c/gcd(a,b)

y=y1*c/gcd(a,b);

书上的题目

线段上格点的个数 gcd
像这种格点很容易和gcd或者欧拉函数结合起来
题目的形象化表示如图:
在这里插入图片描述
所以答案是gcd(x,y)-1。减去1是因为i=gcd(x,y)的时候,就是线段的一个端点,不应该算进去。

双六 exgcd
得看出一个式子
ax+by=1
首先说明:一定存在整数对(x,y)使得ax+by=gcd(a,b)
当1不是gcd(a,b)的时候是无解的。
当1是gcd(a,b)的时候用exgcd来求解(x,y)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值