数论笔记(gcd、快速幂模板 + 埃筛欧筛 + 小定理公式)

求两数的最大公约数gcd / 最小公倍数lcm

  • 最大公约数(gcd)

更相减损法 在时间上显然没有 辗转相除法 优越,这里就不提了

循环写法:

int gcd(int a, int b) {
	int t = a % b;
	while (t)
	{
		a = b;
		b = t;
		t = a % b;
	}
	return b;
}

递归写法:

int gcd(int a, int b) {
	return b ? gcd(b, a % b) : a;
}
  • 最小公倍数(lcm)

两数的乘积 == 这两个数的 最大公约数gcd * 最小公倍数lcm

————————————————————————————————————————

快速幂 / 快速乘

如何快速地计算 a b a^b ab ,在 b b b 特别大的时候?

b 特别大的话循环乘会超时,快速幂的思想就是用二进制压缩乘的过程

  • 快速幂
typedef long long ll;
ll ksm(ll a, ll b, ll mod) {
	//求a的b次方,b特别大的话循环乘会超时,用二进制压缩也就是快速幂
	ll ans = 1, rdw = a;
	while (b)
	{
		//将 b 转化为二进制数看待,设b = 11,b(2) = 1011 = 2^3 * 2^1 * 2^0
		//二进制数为 1 的位置是我们要累计的
		if (b & 1)ans = (ans * rdw) % mod;
		rdw = (rdw * rdw) % mod;//rdw 一路记录 2 的次方,有 1 的时候交给 ans
		b >>= 1;
		//枚举二进制位
	}
	return ans;
}

但是 r d w = ( r d w ∗ r d w ) 取模 m o d rdw=(rdw*rdw)取模mod rdw=(rdwrdw)取模mod 的过程中,指数爆炸还是有可能导致 long long 都存不下的

所以就引入了快速乘的概念, a ∗ b a*b ab == b 个 a 相加 b个a 相加 ba相加

  • 快速乘
ll ksc(ll a, ll b, ll mod) {
	//求 a*b ,两数都很大的话有可能爆 ll ,用二进制 延长 取模的过程,使得大数被 mod 小
	ll ans = 0, rdw = a;
	while (b)
	{
		//b 个 a 也是可以由 b 拆分成多个二进制状态 累加得来
		//拆分会使数尽可能小,能被 mod 得更 “平滑”,不容易爆 ll
		if (b & 1)ans = (ans + rdw) % mod;
		rdw = (rdw + rdw) % mod;
		b >>= 1;
	}
	return ans;
}

综合:

typedef long long ll;
ll ksc(ll a, ll b, ll mod) {
	ll ans = 0, rdw = a;
	while (b)
	{
		if (b & 1)ans = (ans + rdw) % mod;
		rdw = (rdw + rdw) % mod;
		b >>= 1;
	}
	return ans;
}

ll ksm(ll a, ll b, ll mod) {
	ll ans = 1, rdw = a;
	while (b)
	{
		if (b & 1)ans = ksc(ans, rdw, mod);
		rdw = ksc(rdw, rdw, mod);
		b >>= 1;
	}
	return ans;
}

快速幂求逆元(除法取模)

  • a / b a/b a/b 取余 mod == a ∗ b m o d − 2 a*b^{mod-2} abmod2 % mod
  • 要保证 b 和 mod 互质

————————————————————————————————————————

埃筛/欧筛

如何在 O ( n l o g l o g n ) O(nloglog_n) O(nloglogn) 甚至在 O ( n ) O(n) O(n) 的时间内求出 n 以内所有的质数?

原理、证明比较复杂的我也半斤八两,这里只给模板

洛谷:P1217 [USACO1.5]回文质数

图片摘于 小白的学习笔记 的博客

板子

bool prime[N];//false代表素数
int tmp[N];
void orashai(int m) {
    //ora[1] = false
    int cnt = 0;
    for (int i = 2; i <= m; i++) {
        if (!prime[i])
            tmp[cnt++] = i;
        for (int j = 0; j < cnt && i * tmp[j] <= m; j++) {
            prime[i * tmp[j]] = true;
            if (i % tmp[j] == 0)break;
        }
    }
}

埃氏筛法

在这里插入图片描述

  • 遍历 n 的过程中如果找到一个素数,这个素数的倍数一定不是素数,可以标记一下,这样到最后所有未被标记过的数就是 n 以内的素数

  • 还可以简单优化一下,遍历 n 只需要遍历到 n \sqrt{n} n 即可,二重循环倍数的时候同样会把 n \sqrt{n} n 后的非素数标记

  • 时间复杂度为 O ( n l o g l o g n ) O(nloglog_n) O(nloglogn) (有常数)

代码:

bool altshai[N];//false代表素数
void ashai(int m) {
	//altshai[1] = false
	int sqrtm = sqrt(m);
	for (int i = 2; i <= sqrtm; i++) {
		if (altshai[i])continue;//非素数跳过
		for (int j = i * 2; j <= m; j += i)//标记所有素数的倍数
			altshai[j] = true;
	}
}

欧拉筛

  • 在埃拉托筛的基础上优化

  • 埃拉托筛在遍历的素数取倍数的过程中,会有重复数据,如6是2、3素数的倍数,会重复标记,形成冗杂

  • 欧拉筛严谨地排除了这些冗余,使得时间复杂度妥妥地在 O ( N ) O(N) O(N)

  • 初始定义两个数组,ora 储存素数标记,sushu 储存用以乘积的因子(也就是素数),可以理解成倍数数组,实际上由于 sushu 数组储存的只是素数,所以数组大小可以不用开那么大

代码:

bool ora[N];//false代表素数
int sushu[N], scnt;
void orashai(int m) {
	//ora[1] = false
	for (int i = 2; i <= m; i++) {
		if (!ora[i])sushu[++scnt] = i;
		for (int j = 1; j <= scnt && i * sushu[j] <= m; j++) {
			ora[i * sushu[j]] = true;
			if (i % sushu[j] == 0)break;
		}
	}
}
bool ora[N];//false代表素数
int sushu[N];
void orashai(int m) {
	//ora[1] = false
	int cnt = 0;
	for (int i = 2; i <= m; i++) {
		if (!ora[i])//如果是素数,就添加至倍数数组
			sushu[cnt++] = i;
		for (int j = 0; j < cnt; j++) { 
			if (i * sushu[j] > m)break;//只求范围内,超出就退出

			ora[i * sushu[j]] = true;//某数 * 素数 必不可能是素数

			if (i % sushu[j] == 0)break;//重点
			//一旦是因子的倍数就退出
			//除去重复的判断点
		}
	}
}

求组合数

若要求预处理大量组合数

for (int i = 0; i < N; i++)
    {
        for (int j = 0; j <= i; j++)
        {
            if (!j) c[i][j] = 1;
            else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;

            //要求的是 i 个苹果中取 j 个的方案数
            //在 i 中挑出一个苹果,分选和不选这个苹果,可以把集合分成不重不漏的两大类:

            //选:剩下的苹果就在 C[i - 1][j - 1] 中获得
            //不选:剩下的苹果就在 C[i - 1][j] 中获得
        }
    }

若只要求得到单个组合数

C[i][j] = i! / (j! * (i - j)!)
//所以只需要预处理阶乘即可,但这里有除法,有可能涉及逆元 

快速幂求逆元(除法取模)

  • a / b a/b a/b 取余 mod == a ∗ b m o d − 2 a*b^{mod-2} abmod2 % mod
  • 要保证 b 和 mod 互质

————————————————————————————————————————

小定理 / 公式

  • 判断能否 整除十以内 的数的技巧

在这里插入图片描述

  • 数论初等定理

Acwing:买不到的数目

在这里插入图片描述
数学论证:

在这里插入图片描述
证明:

在这里插入图片描述
结论:组合表达出来的糖数可以是 a ∗ x + b ∗ y a*x+b*y ax+by,可以证得大于 a ∗ b − a − b a*b-a-b abab 的任何值都可以用此表示出来

  • 数论小知识

“如果一个数是两个不同质数的乘积”,那这个数就不可能是一个质数和一个非质数的乘积,因为质数不可再分

但如果没有这个前提,一个数确实有这两种乘积的可能性

  • 调和级数优化快速求因子
vector<int> p[M];

void init() {
    for (int i = 1; i < M - 5; i++)
        for (int j = i; j < M - 5; j += i)
            p[j].push_back(i);
}

数论 d p 中常见调和计数优化转移过程,调和级数的时间复杂度是 N l o g N 数论 dp 中常见调和计数优化 转移过程,调和级数的时间复杂度是 NlogN 数论dp中常见调和计数优化转移过程,调和级数的时间复杂度是NlogN

  • 调和级数素数筛
bool is[N];
void shai(int n) {
    for (int i = 2; i <= n; i++) {
        if (!is[i]) {
            for (int j = i + i; j <= n; j += i) {
                is[j] = true;
            }
            //可以添加需要维护的状态
        }
    }
}

第一层循环只走到根号的话就等于埃筛了 第一层循环只走到根号的话就等于埃筛了 第一层循环只走到根号的话就等于埃筛了

虽然没有欧拉筛那么快,但优点是能维护状态 虽然没有欧拉筛那么快,但优点是能维护状态 虽然没有欧拉筛那么快,但优点是能维护状态

  • 质因数分解
vector<int> vec;
for (int j = 2; j * j <= a[i]; j++) {
       int sum = 0;
       while (a[i] % j == 0)
       {
           sum++;
           a[i] /= j;
       }
       if (sum)vec.push_back(j);
   }
if (a[i] > 1)vec.push_back(a[i]);

// 1 不是质数,不属于质因数

独特性质: 任意数都是由全部质因数 但 不同次方 凑出,所以可以通过质因数乘积来分类

  • 数列求和

1 ∗ 2 + 2 ∗ 3 + 3 ∗ 4 + . . . + n ∗ ( n + 1 ) = ( 1 2 + 1 ) + ( 2 2 + 2 ) + ( 3 2 + 3 ) + . . . + ( n 2 + n ) = ( 1 2 + 2 2 + . . . . n 2 ) + ( 1 + 2 + . . . + n ) 1*2+2*3+3*4+...+n*(n+1) =(1²+1)+(2²+2)+(3²+3)+...+(n²+n) =(1²+2²+....n²)+(1+2+...+n) 12+23+34+...+n(n+1)=(12+1)+(22+2)+(32+3)+...+(n2+n)=(12+22+....n2)+(1+2+...+n)

自然数平方数列求和公式 S n = n ( n + 1 ) ( 2 n + 1 ) / 6 S_n=n(n+1)(2n+1)/6 Sn=n(n+1)(2n+1)/6

自然数列求和公式 S n = n ( n + 1 ) / 2 S_n=n(n+1)/2 Sn=n(n+1)/2

两个相加化简得: S n = n ( n + 1 ) ( n + 2 ) / 3 S_n=n(n+1)(n+2)/3 Sn=n(n+1)(n+2)/3

等差数列公式: a n = a 1 + ( n − 1 ) d a_n=a_1+(n-1)d an=a1+(n1)d
前 n 项和公式为: S n = n ∗ a 1 + n ( n − 1 ) d / 2 Sn=n*a_1+n(n-1)d/2 Sn=na1+n(n1)d/2 S n = n ( a 1 + a n ) / 2 Sn=n(a_1+a_n)/2 Sn=n(a1+an)/2

等比数列公式:
a n = a 1 ∗ q ( n − 1 ) a_n=a_1*q^{(n-1)} an=a1q(n1)
前 n 项和公式为: S n = ( a 1 ∗ ( 1 − q n ) ) / 1 − q Sn=(a_1*(1-q^n))/1-q Sn=(a1(1qn))/1q S n = ( a n q − a 1 ) / q − 1 Sn=(a_nq-a_1)/q-1 Sn=(anqa1)/q1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值