基础数论算法(1)整除与同余

本文介绍了数论中的基础概念——整除和同余。讲解了整除的定义、基本性质和特殊倍数性质,通过例题展示实际应用。接着探讨了同余的含义和基本性质,并介绍了快速幂取模这一重要模板,结合具体问题阐述其应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

只是想系统的学一下数论而已orz于是打算写这么一个系列
参考的书目是《信息学奥赛之数学一本通》,不过这书特别的坑所以大概只会选择其中一部分来说说(应付NOIP就行了在意那么多做什么hhh)
此外会参考前几日培训时zhx的讲课内容以及P2O5dalao的笔记。至于剩余的参考内容等,到了涉及的时候再说吧。


(I)整除

整除是数论的重要基础。

一、整除的定义与符号表示

设a为非零整数,b为整数。若 q∈Z,满足b=a·q,则称b可被a整除或a整除b,记作a|b,同时b是a的倍数,a是b的因子。

二、整除的基本性质

(1)a|b,b|c a|c
(2)a|b,a|c x,yZ,a|(b×x+c×y)
(3) m0,a|b(ma)|(mb)
(4) x,yZ,a,b使ax+by=1,a|n,b|n(ab)|n

证明:
a|n,b|n
ab|bn,ab|an(3)
ab|(anx+bny)
ab|n
Q.E.D

(5)b=qd+c,则d|b d|c

三、一些特殊的倍数性质(剪枝什么的最有爱了)

2的倍数 不用说吧,其实a&1什么的也够快了
3的倍数 同样不用说
5的倍数 末位5或0
11的倍数 奇数位与偶数位之差
7、11、13的倍数 末三位与前几位之差是7/11/13倍数(可以递归多幸福
2n 的倍数 后n位是 2n 的倍数

四、一道例题

有一个密码箱,0~n-1中某些整数是它的密码,且满足:如果a,b是它的密码,那么(a+b)%n是它的密码。某人试了k次密码,前k-1次均失败,第k次成功了。问该密码箱最多有多少不同密码。
输入:n,k,试的序列
输出:结果
范围:1 k 250000,k n 1014

显然我们没有什么思路,就需要数学推理。
引理 不定方程ax+by=c( a,b,cN )有整数解的充要条件是gcd(a,b)|c,且解为

x=x0+bgcd(a,b)×ty=y0agcd(a,b)×ttN

gcd(a,b)表示a,b最大公因数,证明各位可以自己思考(其实我不会)
不妨设x为密码,x·k-n·c=gcd(x,n),该方程若有整数解则gcd(x,n)|gcd(x,n),显然成立。故存在k使得x·k%n==gcd(x,n)。
再来看题意,由于x为密码,(x+x)%n就位密码,从而,(2x%n+x)%n为密码。以此类推,x·k%n为密码,即gcd(x,n)为密码。
假设x,y为密码,由题意(px+cy)%n为密码,即ax+by=gcd(x,y)有解。从而,ax+by gcd(x,y)有解,然后根据一系列复杂的推理(我也很绝望啊,完全看不懂QWQ),我们得出gcd(x,y)为解。
那么我们就可以求出一个最小的x,其余密码集合所有数都是它的倍数,而求出x的方法就是先枚举出所给密码a[k]的所有因数,然后通过划去所有gcd(a[i],a[k]),找出目标的x,答案为n/x。
嗯……不知道各位看懂没有,反正我没有。但做法记住就好,实现:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n,k,cnt,a[250005],q[250005];
bool flag[250005];
int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}
int main(){
    scanf("%I64d%I64d",&n,&k);
    for(int i=1;i<=k;++i){
        scanf("%I64d",&a[i]);
    }
    a[k]=gcd(a[k],n);
    for(int i=1;i<=k;++i){
        a[i]=gcd(a[i],a[k]);
    }
    for(LL i=1;i*i<=a[k];++i){//处理所有因数
        if(a[k]%i==0){
            q[++cnt]=i;
            if(i*i!=a[k])
                q[++cnt]=a[k]/i;
        }
    }
    sort(q+1,q+cnt+1);
    for(int i=1;i<k;++i)//枚举出所有q中不正确的因子
        flag[lower_bound(q+1,q+cnt+1,a[i])-q]=1;
    for(int i=1;i<=cnt;++i){//将这些不正确因子的因子去除
        if(f[i]){
            for(int j=1;j<i;++j)
                if(a[i]%a[j]==0)
                    flag[j]=1;
        }
    }
    for(ans=1;f[ans];++ans);//找出最小符合条件的x
    printf("%d\n",n/q[ans]);
    return 0;

}

(II)同余

一、什么是同余

如果a%m=b%m,则称a,b同余,记作 ab (mod m)
(我就不放标准数学定义咬我啊)

二、同余的基本性质

1、a a(mod k)
2、a b(mod k) b a(mod k)
3、a b(mod k),b c(mod k) a c(mod k)
4、a b(mod k) a+c b+c(mod k)
5、a b(mod k),c d(mod k) ac bd(mod k)
6、a b(mod k) anbn (mod k)
推论
1、a·b%k=(a%k)·(b%k)%k
2、a%p=x,a%q=x,且gcd(p,q)=1,则a%(p·q)=x
注意:
a b(mod k)不一定有 anbn (mod k)成立

三、重要模板:快速幂+取模

必背模板,频率挺高的。
该模板的用处在于:在O(logn)的时间内求出 ap%k 的值。
我们的做法是:将每一步分解。例如, 389 = (((((32)23)23)2)2)23 ,没错就类似于秦九韶算法,在每次乘法时加一次取模运算即可实现。
代码(zhx Ver.):

int ksm(int a,int x,int mod)
{
    ans=1;
    while(x)
    {
        if (x&1) ans=(ans*a) % mod;//x为奇数
        A=(a*a) % mod;
        x>>1;//X除以2
    }
}

四、仍然是一道问题

(POJ3292)
设集合H{x|x=4k+1,k N },如果m H,且m能且只能分解为两个合数,且这两个合数只能被分解为两个素数,则称m为H-合成数。
例如,441=21*21=9*49,其中21=3*7,9=3*3,49=7*7.
输入n行,每行有一个整数pi,要求0-pi间H-合成数的个数。

首先我们先将所有H素数筛出来。H数有一个性质:i为H素数,则5i+4i*k是H数但不是H素数。因为显然(5i+4ik)%4=5i%4=(5%4)*(i%4)%4=1.且该数因为有因子i,一定是合数。
以此可以方便的筛出Hprime,然后再讨论即可。实现:

#include <bits/stdc++.h>
using namespace std;
#define MAXN 1000005
bool iHpr[MAXN],iHse[MAXN];
int Hpr[MAXN],acc[MAXN],n;
int main(){
    for(int i=5;i<MAXN;i+=4){//筛H素数与可分解H素数之积的H合数
        if(iHpr[i]) continue;
        Hpr[++n]=i;
        for(int k=i*5;k<MAXN;k+=4*i)
            iHpr[k]=true;
    }
    for(int i=1;i<=n;++i)//求H合成数
        for(int j=1;j<=i&&Hpr[i]*Hpr[j]<MAXN;++j)
            iHse[Hpr[i]*Hpr[j]]=true;
    for(int i=1;i<MAXN;++i)//递推结果
        acc[i]=acc[i-1]+iHse[i];
    int h;
    cin>>h;
    while(h!=0)
        cout<<acc[h],cin>>h;
    return 0;
}

略有些懵逼的话可以输出一下Hpr这个数组看看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值