整除、同余、质数筛法、模运算及快速幂相关

本文深入探讨数论基础知识,包括整除、同余、模运算的性质,以及快速幂取模、最大公约数与最小公倍数的概念和计算方法。通过具体实例,讲解了裴蜀定理的证明及应用,为解决复杂数学问题提供了有效算法。

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

整除、同余

aaa,bbb为整数,a≠0a\neq 0a=0 如果存在一个整数q,使得a×q=ba\times q=ba×q=b,则bbb能被aaa整除,记为a∣ba\mid bab,且称bbbaaa的倍数,aaabbb的因子。

整除的几个性质:

  1. 传递性:如果a∣ba|babb∣cb|cbc,则a∣ca|cac
  2. a|b且a|c等价于对于任意的整数x,y,有a|(bx+cy)
  3. 设m不为0,则a|b等价于ma|mb
  4. 设整数x,y满足下式:ax+by=1,且a|n,b|n,那么(ab)|n
  5. 若b=q*d+c,那么d|b的充要条件是d|c.

同余的几个性质:

  1. 同加性:若a≡b(modp),a\equiv b \pmod p,ab(modp),a+c≡b+c(modp)a+c \equiv b+c \pmod pa+cb+c(modp)
  2. 同减性:若a≡b(modp),a\equiv b \pmod p,ab(modp),a−c≡b−c(modp)a-c \equiv b-c \pmod pacbc(modp)
  3. 同乘性:若a≡b(modp),a\equiv b \pmod p,ab(modp),a×c≡b×c(modp)a \times c \equiv b\times c \pmod pa×cb×c(modp)
  4. 同除性:若a≡b(modp)a\equiv b \pmod pab(modp),且c∣a,c∣b,(c,p)=1c \mid a,c\mid b,(c,p)=1ca,cb,(c,p)=1,则a/c≡b/c(modp)a/c \equiv b/c \pmod pa/cb/c(modp)
  5. 同幂性:若a≡b(modp),c>0,a\equiv b \pmod p,c>0,ab(modp),c>0,ac≡bc(modp)a^c \equiv b^c \pmod pacbc(modp)
  6. a%p=x,a%q=xa\%p=x,a\%q=xa%p=x,a%q=x,且p,qp,qp,q互质,则a%(p∗q)=xa\%(p*q)=xa%(pq)=x

数论小常识:

  1. 222能整除aaa的最末位,则2∣a2|a2a
  2. 444能整除aaa的末两位,则4∣a4|a4a
  3. 888能整除aaa的末三位,则8∣a8|a8a
  4. 333能整除aaa的各位数字之和,则3∣a3|a3a
  5. 999能整除aaa的各位数字之和,则9∣a9|a9a
  6. 111111能整除aaa的偶数位数字之和与奇数位数字之和的差,则11∣a11|a11a
  7. 能被777111111131313整除的数的特征是:这个数的末三位与末三位以前的数字所组成数之差能被777111111131313整除。

模运算

对于整数a,b,其中b不为0,求a除以b的余数,称为a模b,记为a%ba\%ba%b.
模运算的性质:

  1. 分配率:模运算对加、减、乘具有分配率
    (a+b)%c=(a%c+b%c)%c(a+b)\%c=(a\%c+b\%c)\%c(a+b)%c=(a%c+b%c)%c
    (a−b)%c=(a%c−b%c)%c(a-b)\%c=(a\%c-b\%c)\%c(ab)%c=(a%cb%c)%c
    (a∗b)%c=(a%c∗b%c)%c(a*b)\%c=(a\%c*b\%c)\%c(ab)%c=(a%cb%c)%c
    (ab)%c=(a%c)b%c(a^b)\%c=(a\%c)^b\%c(ab)%c=(a%c)b%c
  2. 放缩性
    如果a%b=c,d≠0a\%b=c,d \neq 0a%b=c,d=0,则有(a∗d)%(b∗d)=c∗d(a*d)\%(b*d)=c*d(ad)%(bd)=cd;
    如果a%b=c,d∣a,d∣ba\%b=c,d\mid a,d \mid ba%b=c,da,db,则(a/d)%(b/d)=(c/d)(a/d)\%(b/d)=(c/d)(a/d)%(b/d)=(c/d).
    根据放缩性,则除法取余有这个式子:
    a/b%c=a%(b∗c)/ba/b\%c=a\%(b*c)/ba/b%c=a%(bc)/b

快速幂取模

已知a,b,pa,b,pa,b,p为整数,求ab%pa^b\%pab%p的结果。
可以用二进制倍增的思想,快速求幂。
a的b次方,可以看做是b个a相乘,b可以拆成2的幂的和,于是可以求出a的1次方,a的2次方,a的4次方,…\dots,只要b的二进制的从右数第k位为1,则a的2k2^k2k方乘到结果里即可。
代码非常短小精悍:

LL ksm(LL a,LL b,LL p)
{
	LL res=1;
	while(b)
	{
		if(b&1)res=res*a%p;
		a=a*a%p;
		b>>=1;
	}
	return res;
}

类似的,还有快速乘取模,也是一样运用2进制倍增的思想。
例:求a∗b%pa*b\%pab%p的结果,其中a,b,p都在长整型范围以内。保证p的两倍不超过长整型。
分析:如果两个大整数相乘,结果取幂,虽然结果在整型范围以内,但是中间结果可能超过长整型。
所以可以把乘法操作用快速加来代替。

void ksc(LL a,LL b,LL p) //求a*b%p
{
	LL res=0; //此处要初始化为0.
	a%=p;
	while(b)
	{
		if(b&1)res=(res+a)%p;
		a=(a+a)%p;
		b>>=1;
	}
	return res;
}

有了快速乘以后,对于长整型的幂取模,我们也可以解决了。使用快速加来代替乘法即可。
例:求ab%pa^b\%pab%p的结果,a,b,pa,b,pa,b,p的值不超过101610^161016.
分析:如果使用乘法,则两个长整型数相乘,结果可能超出长整型。所以可以用快速加来代替乘法。
代码如下:

#include<bits/stdc++.h>
using namespace std;
#define LL long long int
LL a,b,c;
LL ans;
LL quickmul(LL a,LL b,LL p)
{
    LL res=0;
    while(b)
    {
        if(b&1)res=(res+a)%p;
        a=(a+a)%p;
        b>>=1;
    }
    return res;
}

LL quickpow(LL a,LL b,LL p)
{
    LL res=1;
    while(b)
    {
        if(b&1)res=quickmul(res,a,p);
        a=quickmul(a,a,p);
        b>>=1;
    }
    return res;
}
int main()
{
    while(scanf("%lld%llld%lld",&a,&b,&c)!=-1,a||b||c)
    {
        ans=quickpow(a,b,c);
        printf("%lld\n",ans);
    }
    return 0;
}

例1:给出一个整数nnn,求nnn^nnn的最低位的值。n<=1016n<=10^{16}n<=1016
分析:本题即是求nnn^nnn模10的结果。
HDU1061
参考代码略。

例2:给出一个整数n,求有多少个整数k,满足k^k<n.
1≤n≤10181\leq n\leq 10^{18}1n1018
分析:观察到k其实很小。所以可以直接枚举k,求k的快速幂。
但是因为nnn很大,所以k的快速幂可能会溢出。
注意:溢出不一定为负数。
本题可以打表,可以发现k不大于15.
如果不大表,一定要防止溢出。因为溢出过后,实际值一定是超过了n的。但是如何发现它溢出了呢?
可以再用除法检测一下。
参考代码:

#include<bits/stdc++.h>
using namespace std;
#define LL long long int
LL qpow(LL a,LL b)
{
    LL res=1,tmp;
    while(b)
    {
        if(b&1)
        {
            tmp=res;
            res=res*a;
            if(a==0||res/a!=tmp)return 0;
        }
        tmp=a;
        a=a*a;
        if(a/tmp!=tmp)return 0;
        b>>=1;
    }
    return res;
}
LL n;
int main()
{
    while(cin>>n)
    {
    LL lz=1;
    while(1)
    {
        LL res=qpow(lz,lz);
        if(res>n||res<=0)break;
        lz++;
    }
    cout<<lz-1<<endl;
    }
    return 0;
}

例3:有一个n的排列,如果存在一个位置1<=i<=n1<=i<=n1<=i<=n,使得前iii个数单调递增或递减,第i+1i+1i+1个数到第nnn个数也单调递增或递减。则称它为漂亮的。问漂亮的n排列有多少个?你只需要输出答案模ppp的结果。1≤n,p≤10181\leq n,p\leq10^{18}1n,p1018
source:hdu5187
分析:可以找规律,得到递推式为ans=2n−2ans=2^n-2ans=2n2.
这个式子其实也可以推出来。
先考虑从大到小依次选择这n个数来摆放。从第二个数开始,它只能放在已放好的那些数的左边或右边,这样形成的是一个单峰的排列。一共有2n−12^{n-1}2n1种。这里边包含了所有元素单调递增或所有元素单调递减的排列。
单谷排列也类似的,只需要由小到大选择这n个数即可。摆放的方法是一样的。这又有2n−12^{n-1}2n1种。然后其中也包含了单调递增或单调递减这两种方案,这两种在单峰排列中已经算过了。
所以答案ans=2∗2n−1−2=2n−2ans=2*2^{n-1}-2=2^n-2ans=22n12=2n2
考虑一下特殊的几种情况:比如p=1或n=1等等。
然后因为n,pn,pn,p都是长整型,所以快速幂可能爆长整型,要用快速加来代替乘法。
参考代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
#define LL long long int
LL n,p;
LL qmul(LL a,LL b,LL p)
{
    LL res=0;
    while(b)
    {
        if(b&1)res=(res+a)%p;
        a=(a+a)%p;
        b>>=1;
    }
    return res;
}
LL qpow(LL n,LL p)
{
    LL res=1,a=2;
    while(n)
    {
        if(n&1)res=qmul(res,a,p);
        a=qmul(a,a,p);
        n>>=1;
    }
    return res;
}

int main()
{
    while(scanf("%lld%lld",&n,&p)!=-1)
    {
        if(p==1)printf("0\n");
        else if(n==1)printf("%d\n",n);
        else printf("%lld\n",(qpow(n,p)+p-2)%p);
    }
    return 0;
}

例4:给两个整数n和k,令f(i)=ikf(i)=i^kf(i)=ik,求f(1)+f(2)+⋯+f(n)f(1)+f(2)+\dots+f(n)f(1)+f(2)++f(n)的值。这个值可能很大,你只需要输出结果模109+710^9+7109+7的值。最多有T组数据。
1<=n<=10000,0<=k<=5,T<=201<=n<=10000,0<=k<=5,T<=201<=n<=10000,0<=k<=5,T<=20
分析:k很小,直接模拟即可。

例5:给出n对整数a和b,和一个模数m,求(a1b1+a2b2+⋯+akbk)%m(a_1^{b_1}+a_2^{b_2}+\dots+a_k^{b_k})\%m(a1b1+a2b2++akbk)%m的值。
poj1995

质数及其筛法

如果一个自然数a,只能被1和它自身整除,且不能被其他数整除,则a为质数。换句话说,质数只有两个因子。
注意:1不是质数。
100以内的质数有:2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,91,97
例1:判断一个数n是否为质数。
可以枚举[2,n−1][2,n-1][2,n1],如果n在这个范围内有因子,则n不是质数。这个方法的时间复杂度为O(n)O(n)O(n)
更好的方法是枚举[2,(n)][2 ,\sqrt(n)][2,(n)],时间复杂度为O(sqrt(n))。O(sqrt(n))。O(sqrt(n))因为n的因子是成对出现的,如果i∣ni | nin,则ni∣n\frac n i |ninn,而i,nii,\frac n ii,in 必有至少一个在[2,(n)][2,\sqrt(n)][2,(n)]内。
参考代码如下:

#include<bits/stdc++.h>
using namespace std;
int n;
int main()
{
	scanf("%d",&n);
	for(int i=2;i<=sqrt(n);i++)
	if(n%i==0)
	{
		printf("n is not a prime\n");
		return 0;
	}
	printf("n is a prime\n");
	return 0;
}

例2:请输出[1,n][1,n][1,n]内的所有质数。每行一个质数。n<=100000。
可以利用例1中判断质数的方法,将1~n的所有数都判断一下,如果是质数,就输出它。这种方法的时间复杂度为O(nn)O(n\sqrt n)O(nn)
代码如下:

#include<iostream>
#include<cmath>
using namespace std;
int n;
bool isprime(int n)
{
	for(int i=2;i*i<=n;i++)
	{
		if(n%i==0)return 0;
	}
	return 1;
}
int main()
{
	scanf("%d",&n);
	for(int i=2;i<=n;i++) //将i=1排除掉。
		if(isprime(i))printf("%d\n",i);
	return 0;
}

这个方法还是太慢了。毕竟时间复杂度是O(NN)O(N\sqrt N)O(NN)
下面介绍两种更快的质数筛法。

埃氏筛法.

时间复杂度为O(NlogN)O(NlogN)O(NlogN)
算法是这样的:先把n以内的2的倍数(不包含2)全部删除,再把n以内的3的倍数(不包含3)全部删除,……,这样做下去,最后剩下的数就全部是质数。
这里的删除其实不算真的删除,只是打上一个标记而已。
每个数都会被它的质因子打一次标记,而一个数的质因子个数不超过log,所以,这个时间复杂度为O(NlogN)O(NlogN)O(NlogN)
代码参考如下:

#include<iostream>
using namespace std;
#define MAXN 1000005
bool flgs[MAXN];//flgs[i]==0表示i为质数 不考虑i<2的情况
int n,cnt,primes[MAXN];
void getprime(int n)
{
	for(int i=2;i<=n;i++)
	{
		if(flgs[i]==1)continue;
		primes[cnt++]=i;
		for(int j=i;j<=n/i;j++)
		flgs[j*i]=1;
	}
}
int main()
{
	scanf("%d",&n);
	getprime(n);
	for(int i=0;i<cnt;i++)
	printf("%d\n",primes[i]);
	return 0;
}

欧式筛法

在上述的埃氏筛法中,一个数可能被它的各个质因子都筛了一遍,而一个数在质因子是不会超过logNlogNlogN的,所以时间复杂度为O(NlogN)O(NlogN)O(NlogN)。而欧式筛法保证每个数只被它的最小质因子筛一遍,这样,时间复杂度为O(N)O(N)O(N)
欧式筛法的过程是这样的:
有一个质数集合Q,一开始,质数集合为空,将最小的质数2加入集合。此时Q={2}Q=\{2\}Q={2}
从2开始枚举依次枚举倍数a,如果倍数a为质数,即将a加入集合Q。
将质数集合中的每个质数的a倍都删掉,因为这些数都是合数。删除指的是打上删除标记,不需要真的删除掉。显然,未打删除标记的数都是质数了。
比如当前质数集合为Q={p1,p2,…,pk}Q=\{p_1,p_2,\dots,p_k\}Q={p1,p2,,pk},当前枚举到的倍数为aaa,如果a没有删除标记,则a为质数,先将a加入集合Q。接下来枚举质数集合中的所有质数,则将a∗p1,a∗p2,…,a∗pka*p_1,a*p_2,\dots,a*p_kap1,ap2,,apk全部打上删除标记。
在当前倍数为a,枚举质数的过程中,若发现a是某个质数pip_ipi的倍数时,此时后续的质数就无需再枚举了,可以提前退出。为什么呢?设后续的质数为pi′,(pi′>pi)p_{i'},(p_{i'}>p_i)pi,(pi>pi),那么那些质数的a倍,其实也是质数pip_ipi的倍数。即a∗pi′=a′∗pia*p_{i'}=a'*p_iapi=api,因为p_i<p_{i’},所以,a′>aa'>aa>a。因为我们要保证每一个数是被它的最小质因子给删掉,a∗pi′a*p_{i'}api应该被pip_ipi删掉,所以就留给倍数a变成a′a'a再去处理了。

例:求给一个整数n,输出区间[1,n]中的所有质数。
n<=1000000
参考代码如下:

#include<bits/stdc++.h>
using namespace std;
#define MAXN 1000005
int prime[MAXN];
bool delflg[MAXN];
int n,cnt;
void sieve(int x)
{
    for(int i=2;i<=x;i++)
    {
        if(delflg[i]==0)prime[cnt++]=i;
        for(int j=0;j<cnt;j++)
        {
            if(prime[j]*i>x)break;
            delflg[prime[j]*i]=1;
            if(i%prime[j]==0)break;
        }
    }
}
int main()
{
    scanf("%d",&n);
    sieve(n);
    for(int i=0;i<cnt;i++)
    {
        printf("%d\n",prime[i]);
    }
    return 0;
}

最大公约数与最小公倍数

一般的,设a1,a2,a3,…,aka_1,a_2,a_3,\dots,a_ka1,a2,a3,akkkk个正整数,如果存在一个正整数ddd,使得d∣a1,d∣a2,…d∣akd| a_1,d\mid a_2,\dots d|a_kda1,da2,dak,那么ddd则为a1,a2,…,aka_1,a_2,\dots,a_ka1,a2,,ak的公约数。在所有公约数中,最大的称为最大公约数,记为gcd(a1,a2,…,ak)gcd(a_1,a_2,\dots,a_k)gcd(a1,a2,,ak),显然它是存在的,至少为1。当gcd=1gcd=1gcd=1时,称这nnn个数是互质的或既约的。公约数一定是最大公约数的约数。
一般的,设a1,a2,a3,……aka_1,a_2,a_3,……a_ka1,a2,a3,akkkk个正整数,如果存在一个正整数ddd,使得a1∣d,a2∣d,a3∣d,……ak∣da_1|d,a_2|d,a_3|d,……a_k|da1d,a2d,a3d,akd,那么ddd则为a1,a2,……aka_1,a_2,……a_ka1,a2,ak的公倍数,其中最小的称为最小公倍数数,记为lcm(a1,a2,……,ak)lcm(a1,a2,……,ak)lcm(a1,a2,,ak),显然它是存在的。公倍数一定是最小公倍数的倍数。
定理:lcm(a,b)×gcd(a,b)=a×blcm(a,b) \times gcd(a,b)=a \times blcm(a,b)×gcd(a,b)=a×b
证明:将a,ba,ba,b进行质因子分解,设a,ba,ba,b的质因子集合并集为{p1,p2,…,pn}\{p_1,p_2,\dots,p_n\}{p1,p2,,pn}

a=p1k1p2k2…pnkna=p_1^{k_1}p_2^{k_2}\dots p_n^{k_n}a=p1k1p2k2pnkn 其中0≤k1,0≤k2,0≤k3,…0≤kn0\leq k_1,0\leq k_2,0\leq k_3,\dots 0\leq k_n0k1,0k2,0k3,0kn
b=p1j1p2j2…pnjnb=p_1^{j_1}p_2^{j_2}\dots p_n^{j_n}b=p1j1p2j2pnjn 其中0≤j1,0≤j2,0≤j3,…,0≤jn0\leq j_1,0\leq j_2, 0\leq j_3,\dots,0\leq j_n0j1,0j2,0j3,,0jn
那么显然
gcd(a,b)=p1min(k1,j1)p2min(k2,j2)…pnmin(kn,jn)gcd(a,b)=p_1^{min(k_1,j_1)}p_2^{min(k_2,j_2)}\dots p_n^{min(k_n,j_n)}gcd(a,b)=p1min(k1,j1)p2min(k2,j2)pnmin(kn,jn)
lcm(a,b)=p1max(k1,j1)p2max(k2,j2)…pnmax(kn,jn)lcm(a,b)=p_1^{max(k_1,j_1)}p_2^{max(k_2,j_2)}\dots p_n^{max(k_n,j_n)}lcm(a,b)=p1max(k1,j1)p2max(k2,j2)pnmax(kn,jn)
∵min(ki,ji)+max(ki,ji)=ki+ji\because min(k_i,j_i)+max(k_i,j_i)=k_i+j_imin(ki,ji)+max(ki,ji)=ki+ji
∴gcd(a,b)∗lcm(a,b)=p1k1+j1∗p2k2+j2…pnkn+jn=a∗b\therefore gcd(a,b)*lcm(a,b)=p_1^{k_1+j_1}*p_2^{k_2+j_2}\dots p_n^{k_n+j_n}=a*bgcd(a,b)lcm(a,b)=p1k1+j1p2k2+j2pnkn+jn=ab

辗转相除法
gcd(a,b)=gcd(b,a%b)gcd(a,b)=gcd(b,a\%b)gcd(a,b)=gcd(b,a%b)
证明:
gcd(a,b)=P,则a=kP,b=gP,gcd(b,agcd(a,b)=P,则a=kP,b=gP,gcd(b,a%gcd(a,b)=P,a=kP,b=gP,gcd(b,a

更相减损术的证明方法和辗转相除法差不多。
中国古代,九章算术中提到了一种求最大公约数的方法,即更相减损术。
如果要对分式a/ba/ba/b约分,如果a,b都是偶数,可以将a,b都折半;否则,将a和b交替地减去对方,直到最后两者相等,那么这个数可以做为原来a,b的最大公约数。

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

裴蜀定理

如果a,ba,ba,b的最大公约数为ddd,且d能整除c,则存在正整数x和y,满足ax+by=c。
证明:
a′=a/d,b′=b/d,则a′x+b′y=1a'=a/d,b'=b/d,则a'x+b'y=1a=a/d,b=b/d,ax+by=1,此时,a′,b′a',b'a,b互质。即我们只需要证明:对于互质的两个整数a′,b′a',b'a,b,必存在一个整数xxx,满足a′x%b′=1a'x\%b'=1ax%b=1 即可。 (1)

引理一:如果a,ba,ba,b为正整数,且a,ba,ba,b互质,则不存在小于bbb的正整数k,使得0≡k∗a(modb)0 \equiv k*a \pmod b0ka(modb)
证明显然。用反证法,如果存在0<k<b0<k<b0<k<b,满足0≡k∗a(modb)0 \equiv k*a \pmod b0ka(modb)
根据整数唯一分解定理,k∗ak*aka中一定包含bbb的所有质因子,而aaabbb互质,aaa中不可能包含bbb中的质因子,所以必然k是b的倍数。而0<k<b0<k<b0<k<b,所以,kkk不可能包含bbb中所有质因子。

推论:如果a,ba,ba,b为正整数,且a,ba,ba,b互质,0,a,2∗a,3∗a,…,(b−1)∗a0,a,2*a,3*a,\dots,(b-1)*a0,a,2a,3a,,(b1)a,这些数模b,余数互不相等。
证明:如果存在两个不同的数设为i∗a,j∗ai*a,j*aia,ja,它们模bbb的余数相等,则(i−j)∗a%b(i-j)*a\%b(ij)a%b模b的余数为000
而因为0<i<n,0<j<n0 \lt i \lt n,0 \lt j \lt n0<i<n,0<j<n,假设i>ji>ji>j,则0<(i−j)<n0 \lt (i-j)\lt n0<(ij)<n,这与引理一矛盾。
得证。

引理二:如果a,ba,ba,b互质,则必存在一个整数kkk,满足k∗a%b=1k*a\%b=1ka%b=1.
证明:根据推论,这些数模b的结果只能是集合{0,1,… b−1}\{0,1,\dots\,b-1\}{0,1,b1}中的数,而这些结果互不相等,且有b个,所以其中必有一个为1。

根据引理2,如果a,ba,ba,b互质,则必存在一个整数kkk,满足k∗a%b=1k*a\%b=1ka%b=1,即k∗a−p∗b=1k*a-p*b=1kapb=1
于是,裴蜀定理得证。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值