c++数论总结

1 篇文章 0 订阅

1.数论总结(本篇章是学习中写的,忘记了便于查找)


整除

若整数b除以非零整数a,商为整数,且余数 [1] 为零,b为被除数,a为除数,即 a|b(“|”是整除符号),读作“a整除b”或“b能被a整除”。a叫做b的约数(或因数),b叫做a的倍数。整除属于除尽的一种特殊情况。

最大公约数

定义
a a a b b b的公共的约数中最大的约数,记作 g c d ( a , b ) gcd ( a , b ) gcd(a,b)

最小公倍数

定义
两个或多个整数公有的倍数叫做它们的公倍数,其中除0以外最小的一个公倍数就叫做这几个整数的最小公倍数, a a a, b b b的最小公倍数计算公式:
l c m ( a , b ) = a ∗ b g c d ( a , b ) ( 注: a 与 b 的最小公倍数是 a 与 b 的乘积除以 a 与 b 的最大公约数 ) lcm(a,b)=\frac{a*b}{gcd(a,b)}\quad{(注:a与b的最小公倍数是a与b的乘积除以a与b的最大公约数)} lcm(a,b)=gcd(a,b)ab(注:ab的最小公倍数是ab的乘积除以ab的最大公约数)

同余

定义
数学上,同余(英语:congruence modulo,符号:≡)是数论中的一种等价关系。当两个整数除以同一个正整数,若得相同余数,则二整数同余。同余是抽象代数中的同余关系的原型。最先引用同余的概念与“≡”符号者为德国数学家高斯。对于模n同余的所有整数组成的这个集合称为同余类(congruence class或residue class)。
同余符号
两个整数 a a a b b b,若它们除以正整数 m m m所得的余数相等,则称 a a a b b b对于模 m m m同余。
记作 a ≡ b ( m o d    m ) a \equiv b (mod\;m) ab(modm)
读作 a a a同余于模 m m m,或读作 a a a b b b关于模 m m m同余。
比如 26 ≡ 14 ( m o d    12 ) 26 \equiv 14 (mod\;12) 2614(mod12)
同余于的符号是同余相等符号≡。统一码值为 U+2261。但因为方便理由,人们有时会把它(误)写为普通等号 (=);
自反性
a ≡ a ( m o d    m ) a \equiv a (mod\;m) aa(modm)
对称性
b ≡ a ( m o d    m ) b \equiv a (mod\;m) ba(modm)
传递性
如果 a ≡ b ( m o d    m ) a \equiv b (mod\;m) ab(modm) b ≡ c ( m o d    m ) b \equiv c (mod\;m) bc(modm) a ≡ c ( m o d    m ) a \equiv c (mod\;m) ac(modm)
同余式相加
如果 a ≡ b ( m o d    m ) a \equiv b (mod\;m) ab(modm) c ≡ d ( m o d    m ) c \equiv d (mod\;m) cd(modm) a ± b ≡ c ± d ( m o d    m ) a±b \equiv c±d (mod\;m) a±bc±d(modm)
同余式相乘
如果 a ≡ b ( m o d    m ) a \equiv b (mod\;m) ab(modm) c ≡ d ( m o d    m ) c \equiv d (mod\;m) cd(modm) a b ≡ c d ( m o d    m ) ab \equiv cd (mod\;m) abcd(modm)
同余式相除
如果 a c ≡ b c ( m o d    m / g c d ( c , m ) ) , c ≠ 0 ac \equiv bc (mod\;m/gcd(c,m)),c\neq0 acbc(modm/gcd(c,m)),c=0 a ≡ b ( m o d    m ) a \equiv b (mod\;m) ab(modm)
同余式的幂
如果 a ≡ b ( m o d    m ) a \equiv b (mod\;m) ab(modm) a n ≡ b n ( m o d    m ) a^n \equiv b^n (mod\;m) anbn(modm)

欧拉函数

定义
在数论,对正整数n,欧拉函数是小于n的正整数中与n互质的数的数目。
(互质是公约数只有1的两个整数,叫做互质整数。公约数只有1的两个自然数,叫做互质自然数,后者是前者的特殊情形)。
列如:
ϕ ( 8 ) = 4 \phi (8) = 4 ϕ(8)=4,即与8的互质数有1,3,5,7,共四个互质数。

标准分解式
x = p 1 n 1 ∗ p 2 n 2 ∗ p 3 n 3 ∗ . . . ∗ p m n m ( 注: p 为 x 的互质数 ) x=p_{1}^{n_{1}}*p_{2}^{n_{2}}*p_{3}^{n_{3}}*...*p_{m}^{n_{m}}{(注:p为x的互质数)} x=p1n1p2n2p3n3...pmnm(注:px的互质数)

欧拉函数计算式:
ϕ ( x ) = x ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ ( 1 − 1 p 3 ) ∗ . . . ∗ ( 1 − 1 p m ) \phi(x)=x*(1-\frac{1}{p_{1}})*(1-\frac{1}{p_{2}})*(1-\frac{1}{p_{3}})*...*(1-\frac{1}{p_{m}}) ϕ(x)=x(1p11)(1p21)(1p31)...(1pm1)

例:
如2022的标准分解式为:
2022= 2 1 ∗ 3 1 ∗ 33 7 1 2^1*3^1*337^1 21313371
ϕ ( 2022 ) = 2022 ∗ ( 1 − 1 2 ) ∗ ( 1 − 1 3 ) ∗ ( 1 − 1 337 ) ) = 672 \phi(2022)=2022*(1-\frac{1}{2})*(1-\frac{1}{3})*(1-\frac{1}{337}))=672 ϕ(2022)=2022(121)(131)(13371))=672

代码附着与验证计算:

//main.cpp
#include <iostream>

int CalculateCoprime()
{
	int n;
	std::cout << "请输入需要计算的n的互质个数:";
	std::cin >> n;
	int cout = n;
	for (int i = 2; i < n; i++)
	{
		if (n % i == 0)
		{
			cout = cout / i * (i - 1);
			while (n % i == 0)
			{
				n /= i;
			}
		}
	}

	if (n > 1)
	{
		cout = cout / n * (n - 1);
	}
	std::cout << "n的互质个数有" << cout << "个" << std::endl;
	system("pause");
	return cout;
}


int main()
{
	return CalculateCoprime();
}

在这里插入图片描述

整除分块

学习莫比乌斯反演,你会发现整除分块这个东西几乎是非常必要的,它可以把一些需要时间复杂度为 O ( n ) O(n) O(n)的枚举优化到 O n O\sqrt n On

整除分块是数论中常用的一种技巧,先从下面问题入手:

已知 f ( n ) = ∑ i = 1 n ⌊ n i ⌋ 注 : ∑ 为求和符号, ⌊    ⌋ 为向下取整 , ⌈    ⌉ 为向上取整 f(n) = \sum_{i = 1}^{n} \left \lfloor \frac{n}{i} \right \rfloor{ \quad注:\sum为求和符号,\lfloor\;\rfloor为向下取整, \lceil\;\rceil为向上取整} f(n)=i=1nin:为求和符号,为向下取整,为向上取整,给定一个 n,求 f(n) 的值;

如果 n n n的取值范围是 1 ⩽ n ⩽ 1 0 6 1 \leqslant n \leqslant 10^6 1n106,毫无疑问,我们会采用直接遍历的方式去计算当前求和的值,便能得到我们的答案,它的时间复杂度是 O ( n ) O(n) O(n)

但如果 1 ⩽ n ⩽ 1 0 9 1 \leqslant n \leqslant 10^9 1n109 ,甚至 1 ⩽ n ⩽ 1 0 20 1 \leqslant n \leqslant 10^{20} 1n1020呢,显然 O ( n ) O(n) O(n)的时间复杂度就完全不能满足我们当前的需求了,这时候我们就得考虑一种新的方式去计算,让代码在时间复杂度上稍微小一点,这就不得不提一提我们整除分块的内容了。

n = 20 n=20 n=20 ⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor in 的值:

i i i1234567891011121314151617181920
⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor in2010654322221111111111

仔细观测我们可以发现,在 ⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor in在某些区间一段它的值其实是一致的,那我没是不是可以按照这些区间将它分成不同的块来进行计算呢,这就是整除分块的核心思想了。

既然分块,那块的边界是啥呢?
我们设左端点是 l l l,右端点是 r r r ∈ [ l , r ] \in[l,r] [l,r],这一块的个数为 k k k,可以知道 k = ⌊ n i ⌋ = ⌊ n l ⌋ k= \lfloor ni\rfloor= \lfloor nl\rfloor k=ni=nl,而 r = m a x ( i ) r=max(i) r=max(i),当 ( i ∗ k < = n ) (i∗k<=n) (ik<=n),所以 i < = n k i<=nk i<=nk,即 r = ⌊ n ⌊ n l ⌋ ⌋ r= \lfloor n\lfloor nl\rfloor\rfloor r=nnl⌋⌋

上代码:

int division_block(int n) 
{
	int res = 0;
	for (int l = 1, r; l <= n; l = r + 1) {
		r = n / (n / l);
		res += n / l * (r - l + 1);
	}
	return res;
}

快速幂

快速幂算法的核心思想就是每一步都把指数分成两半,而相应的底数做平方运算。这样不仅能把非常大的指数给不断变小,所需要执行的循环次数也变小,而最后表示的结果却一直不会变。
3 10 = 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 3^{10}=3*3*3*3*3*3*3*3*3*3 310=3333333333
3 10 = ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) 3^{10}=(3*3)*(3*3)*(3*3)*(3*3)*(3*3) 310=(33)(33)(33)(33)(33)
3 10 = ( 3 ∗ 3 ) 5 3^{10}=(3*3)^5 310=(33)5
3 10 = 9 5 3^{10}=9^5 310=95
9 5 = ( 9 4 ) ∗ ( 9 1 ) 9^5=(9^4)*(9^1) 95=9491
9 5 = ( 656 1 1 ) ∗ ( 9 1 ) 9^5=(6561^1)*(9^1) 95=65611(91)
以下以求a的b次方来介绍 [1]
把b转换成二进制数。
该二进制数第i位的权为 2 i − 1 2^{i-1} 2i1
例如:
a 11 = a 2 0 + 2 1 + 2 3 a^{11}=a^{2^0+2^1+2^3} a11=a20+21+23
11的二进制是1011
11 = 2 3 × 1 + 2 2 × 0 + 2 1 × 1 + 2 0 × 1 11 = 2^3×1+2^2×0+2^1×1+2^0×1 11=23×1+22×0+21×1+20×1
则:
a 11 = a 2 3 × 1 + 2 2 × 0 + 2 1 × 1 + 2 0 × 1 = a 2 3 + 2 1 + 2 0 = a 2 3 × a 2 1 × a 2 0 a^{11}=a^{2^3×1+2^2×0+2^1×1+2^0×1}=a^{2^3+2^1+2^0}=a^{2^3}×a^{2^1}×a^{2^0} a11=a23×1+22×0+21×1+20×1=a23+21+20=a23×a21×a20
常规求幂

int pow1(int a,int b)
{
   int r=1;
   while(b--) r*=a;
   return r;
} 

快速求幂(一般)

int pow2(int a,int b)
{
    int r=1,base=a;
    while(b!=0)
    {
    if(b%2) r*=base;
    base*=base;
    b/=2;
    }
    return r;
}

快速求幂 (递归)

int f(int m,int n)
{   
    if(n==1) return m;
    int temp=f(m,n/2);
    return (n%2==0 ? 1 : m)*temp*temp;
}

快速求幂(位运算)

int pow3(int x, int n)
{
    if(n == 0) return 1;
    int t = 1;
    while(n != 0)
    {
      if(n & 1) t *= x;
      n >>= 1;
      x *= x;
    }
    return t;
}

逆元

若在mod p意义下,对于一个整数a,有 a ∗ b ≡ 1 ( m o d    p ) a*b\equiv1(mod\;p) ab1(modp),那么我们称呼此时的 b b b a a a关于1模的乘法逆元,同时a也为b的乘法逆元;一个数有逆元的充分必要条件是 g c d ( a , p ) = 1 gcd(a,p)=1 gcd(a,p)=1,此时 a a a才有对 p p p的乘法逆元。逆元的数学符号是 i n v inv inv,我们记此时的 b b b i n v ( a ) inv(a) inv(a)或者 a − 1 a^{-1} a1
例: 5 × 3 ≡ 1 ( m o d    14 ) 5×3\equiv1(mod\;14) 5×31(mod14),我们称此时的3为5关于1模14的乘法逆元。

逆元的存在的意义是用来处理除法运算取余问题。
比如:
( a ÷ b ) % p = ( a % p ÷ b % p ) % p (a ÷ b) \% p = (a\%p ÷ b\%p) \%p (a÷b)%p=(a%p÷b%p)%p
这种写法就是错误的,而这里的除法运算如果换算成加减乘,那么一样的是成立的,比如:
( a × b ) % p = ( a % p × b % p ) % p (a × b) \% p = (a\%p × b\%p) \%p (a×b)%p=(a%p×b%p)%p
这样写等号俩边是成立的,但是除法却不行;
在计算过程中,除法可能会造成较大的精度损失,因此,我们一般会把 ( a ÷ b ) % p (a ÷ b) \% p (a÷b)%p转换成:
( a ∗ i n v ( b ) ) % p = ( a % p ∗ i n v ( b ) % p ) % p (a * inv(b) ) \% p = (a \% p * inv(b) \% p) \% p (ainv(b))%p=(a%pinv(b)%p)%p
这样就解决了除法取余不能分开计算的悲哀。但是a,p要满足俩者互质,也就是 g c d ( a , p ) = 1 gcd(a,p)=1 gcd(a,p)=1,a才能找到关于p的逆元。

如何计算逆元?
费马小定理:

当有俩数 a a a, p p p满足 g c d ( a , p ) = 1 gcd(a,p)=1 gcd(a,p)=1,p是质数时,则 a p − 1 ≡ 1 ( m o d    p ) a^{p-1}≡1(mod\;p) ap11modp
变化一下定理可得:
a ∗ a p − 2 ≡ 1 ( m o d    p ) a*a^{p-2}≡1(mod\;p) aap21modp,这时候,我们再看看逆元中的例子,是不是很眼熟。
所以,我们可以使用快速幂求出 a p − 2 a^{p-2} ap2,即求出 a a a的逆元。

附代码:

#include<iostream>
using namespace std;
const int MaxN = 100010;
const int Mod = 1e5 + 7;
__int64 Finv[MaxN];
__int64 Quickpow(__int64 x, __int64 p, __int64 m)//快速幂算法
{ 
	__int64 res = 1;
	while (p) 
	{
		if (p & 1)	res = res * x % m;
		x = x * x % m;
		p >>= 1;
	}
	return res;
}
void Init()//初始化求取逆元
{
	Finv[1] = 1;
	for (int i = 2; i < MaxN; i++)
	{
		Finv[i] = Quickpow(i, Mod - 2, Mod);
	}
}
int main()
{
	Init();
	for (int i = 1; i < MaxN; i++)
	{
		cout << Finv[i] << " ";
	}
	return 0;
}
拓展欧几里得算法

求a关于1模P的逆元 a ≡ 1 ( m o d P ) a\equiv 1(mod P) a1(modP),可以转化为 a ∗ X = 1 + K ∗ P a*X = 1+K*P aX=1+KP,其中 X X X P P P都是整数,这样 X X X即为所求逆元。

这样就可以转化为拓展欧几里得算法(要求 a a a P P P互质): a ∗ X + K Y = 1    ( 这里的 K 代指系数,千万别问为什么 a*X + KY = 1\;(这里的K代指系数,千万别问为什么 aX+KY=1(这里的K代指系数,千万别问为什么aX = 1+KP 移项会变成 移项会变成 移项会变成a*X + KY = 1 ,俩种在俩个式子里面都代指系数 ) ,俩种在俩个式子里面都代指系数) ,俩种在俩个式子里面都代指系数)

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值