文章目录
本文在撰写的时候出现了一些小问题,在第一次撰写时没有注意笔记本电量导致直接关机丢失上千字长文 /哭脸/,在此特别提醒大家在学习或者时使用电脑进行生产力活动的时候要及时保存文件🐂。
本文是数论的开篇之章,会有相关数论学习方法和路线的推荐,大家可以借鉴相关思路,下面我们会一点一点进入数论的学习,希望大家能坚持住数论板块的学习,一定会学有所得的!
慢也好,步子小也好,是在往前走就好。
一, 数论学习路线的介绍和相关建议
本专栏的内容为算法竞赛中的数论内容,来自笔者自身的一些小见解仅作参考,特殊情况应特殊看待。
1,建议学习人群 :
目标赛事奖项为 ICPC区域赛 / 蓝桥杯国赛二等奖以上 / 河北省赛 二等奖以上 ,有一定算法基础并感兴趣的同学(至少要了解一些基础算法)
2,建议学习时长
本专题将会分成四个大部分,建议每一个大部分耗时不要超过20天,总学习时长控制在三个月以内。
3,学习路线的介绍
如下图所示,我们把数论的学习分成了四个板块,由于基础数论的内容非常多,所以在分类的时候我们把基础数论分成了两个板块,分开学习效率更能最大化。
我们在学习数论的前期的时候可以选择性的选择考点比较集中的地方先学习,这样的学习路径可以极大的提高我们的算法实现能力,可以极大的提高我们算法学习的积极性,重点内容 :数论第一阶段,计算几何和组合数学基础内容,按照整个进度学习,虽然体系不完整,但是确实能带来效益的最大化!
当然也可以按照知识体系的顺序学习,这样学习方式比较适合队内的专业数论手,大家可以按照自身的进度调整数论学习路线,下面我们详细的分部分介绍一下所要设计的知识
1,基础数论
首先我们会介绍一下什么是质数,约数,最大公因数... 一些数学定义,在数学中的相关定义和一些取模的技巧的介绍,然后我们就会正式的进入到基础数论的学习中来了,首先是算法竞赛中的快速幂,这个算法结构在算法竞赛中主要用来对一些迭代的程序进行加速的操作!然后我们会介绍一下简单的 GCD和LCM 算法的实现流程,还有素数的筛选,欧拉函数的识别和使用。
第二部分我们主要学习的内容都是和数学中的比较偏门的定理,这块的性价比并不能算的上高,所以这部分的内容可以选择性的跳过,我们只要知道有这个定理,在看题的时候有这么个思路就好,当我们知道这个思路来自数论之后再拿出我们的板子,一般这种题型比较喜欢出现防AK的HANK题中,程度比较弱的同学可以直接跳过。
2,组合数学
组合数学就是我们高中数学中学到的排序和组合问题,只不过会更加的专业,更加的代码化,这部分的内容我认为是比较简单的,尤其是这部分前面的内容,基本就是对高中知识点的复习,这部分题目在贪心题中出现的非常严重,练习这部分的知识,以后就能成为队内的贪心大王,后面的小定理还是了解思路即可,我们目前最主要的方向还是去学习一些简单的知识体系,没有必要去冲击过于困难的内容,毕竟有时候思路出来了,代码手也搓不出来代码
3,计算几何
计算几何整体的难度比较平均,都是中等难度的知识点,建议大家都了解一下,这个对我们对多维图形的模拟很重要,可能会卡在铜牌题的位置,对这部分还是要注意,相关的板子即使背不过,赛时也要带上,我的建议时全部都要了解学习,如果实在是时间有限,可以放弃三维几何部分。
二,基础数论第一部分 ------ 快速幂和快速幂矩阵
1,快速幂
1,解题背景
在一般运算中我们可以用循环的方式来求解N^k^,但是当这个 K 的数据范围超过 10^9^,以后使用循环求解就显的比较无力了,为了给循环提提速,我们提出了快速幂的思想!
2,思想
快速幂实际上就是使用了二进制的思想进行实现,迭代乘法的方式来实现
3,代码
下面是求解 a^n^ 的二次幂代码
cpp
typedef long long LL ;
/*
函数一: fastPow(参数1:表示幂的底数 , 参数2:表示幂的指数 )
// 注意数据类型
函数二 : fastPow(参数1:表示幂的底数 , 参数2:表示幂的指数 , 参数3 : 取模数)
*/
LL fastPow(int a ,int n){
LL ans = 0 ;
while(n){
if(n&1)ans*=a;
a*=a; n >>= 1 ;
}
return ans ;
}
LL fastPow(LL a ,LL n , LL mod){
LL ans = 0 ;
while(n){
if(n&1) ans*=a,ans%=mod;
a%=mod,a*=a,a%=mod;
n>>=1;
}
return ans ;
}
时间复杂度: O(logN)
(扩展)矩阵的计算
在完成了快速幂的讲解之后我们想到了一个问题,这种乘法是知道一个固定的系数的,但是当我们面对更加复杂的矩阵的时候我们应该如何应对?就比如斐波那契数列这种相加关系 A~n+1~ = A~n~ + A~n-1~
这叫要使用我们下面的矩阵,为了保护一些没有学线性代数的同学,我们将介绍一些矩阵中简单的规则。
矩阵的加法 : 对应位置直接加减
矩阵的乘法 : 第一个矩阵的第 i 行乘以第二个矩阵的第 j 列,可以得到 D~i , j~
2,矩阵的快速幂
**思想:**快速幂的思想 + 矩阵的乘法 + 矩阵的结合律
代码:
cpp
matrix operator * ( const matrix &a , const matrix &b ){
matrix c ;
memset(c.m,0,sizeof c.m);
for(int i = 0 ; i < N ; i ++ ){
for(int j = 0 ; j < N ; j ++ ){
for(int k = 0 ; k < N ; k ++ ){
c.m[i][j] = (a.m[i][k] + c.m[i][j] + b.m[k][j]) % mod ;
}
}
}
return c ;
}
matrix pow_matrix(matrix a , int n ){
matrix ans;
memset(ans.m,0,sizeof ans.m);
for(int i = 0 ; i < N ; i ++ ) ans.m[i][i] = 1 ;
while(n){
if(n&1) ans = ans * a;
a = a * a ;
n >>= 1 ;
}
return ans ;
}
实际应用 :
- 斐波那契数列 : 使用矩阵快速幂进行乘法的快速递推能迅速的降低时间复杂度 (本次解题一定要有相关的题解来着重的解释学习的知识占比)
- 所有多项式中但是有系数关系的知关系都可以使用这种方式尝试实现!
1,计算走 K 次能达到的位置 ,使用 1 / 0 法来进行表示
2,计算走 K 次到达每个点的距离长度
相关的代码可以沿用上文的矩阵中的二次幂来解决问题
(矩阵)矩阵加速
在矩阵中很多的高级用法实际上我们是没有做出相关的解释的,本质上是为了快速幂的知点我们能学习的更加的细致,下面我们来解决的问题是矩阵学习中会涉及到的相关问题,我们学习这部分内容能让我们对矩阵加速有更加详细的认识,相比较而言这部分的定理内容对于有线性代数同学还是比较优待的,话不多说先进入正题:
矩阵加速------矩阵快速幂最常用的算法
矩阵加速算法是一种基于矩阵运算的优化方法,主要用于提高计算效率。矩阵快速幂算法是一种用于高效计算矩阵的高次方的算法。它将朴素的时间复杂度O(n)降到了log(n),从而大大提高了计算效率。
该算法的基本原理是将n个矩阵进行两两分组,然后将每组中的矩阵进行乘法运算,从而得到矩阵的n次方。通过利用矩阵乘法的结合律,可以减少重复计算的次数,进一步提高计算效率。具体实现中,可以将幂次转化为二进制形式,然后根据二进制位进行矩阵的分组和乘法运算。这样可以进一步减少计算量,提高计算效率。
简单的来说矩阵快速幂的原理就是,快速幂思想,矩阵乘法只跟左右顺序有关,和先计算那一部分无关 ,使用这个原理我们在推导相关的递推公式就可以在我们的矩阵快速幂模板中实现矩阵加速的操作。
3,课后例题
1,快速幂专区
题目类型 : 模板题
过题代码:
代码一
cpp
#include <iostream>
using namespace std ;
typedef long long LL ;
int main () {
LL a , b , p ;
cin >> a >> b >> p ;
LL c = 1 ;
LL aa = a ;
LL bb = b ;
while(b){
if(b&1){
c %= p ;
c *= a ;
c %= p ;
}
a %= p ;
a *= a ;
a %= p;
b >>= 1 ;
}
cout << aa << "^" << bb << " mod " << p << "=" << c << endl ;
return 0 ;
}
代码二
cpp
#include <iostream>
using namespace std ;
typedef long long LL ;
LL fastpow(LL a , LL b , LL mod ){
LL c = 1 ;
while(b){
if(b&1){
c *= a ;
c %= mod ;
}
b >>= 1 ;
a %=mod ;
a *= a ;
a %=mod;
}
return c ;
}
int main (){
LL a , b , c ;
cin >> a >> b >> c ;
cout << a << "^" << b << " mod " << c << "=" << fastpow(a,b,c)<< endl;
return 0 ;
}
快速幂部分的题型一般都是纯粹的板子题,非常的明显,就不过多的扩展介绍了
2,快速幂矩阵专区
快速幂矩阵有一道模板题,我给大家整理出了来,相关的快速幂矩阵的大量的知识为了体现快速幂的思想我们都没有详细的体现,完成这道题目之后我们将会更加详细的介绍
【模板】矩阵快速幂
题目描述
给定 n × n n\times n n×n 的矩阵 A A A,求 A k A^k Ak。
输入格式
第一行两个整数 n , k n,k n,k。
接下来 n n n 行,每行 n n n 个整数,第 i i i 行的第 j j j 的数表示 A i , j A_{i,j} Ai,j。
输出格式
输出 A k A^k Ak
共 n n n 行,每行 n n n 个数,第 i i i 行第 j j j 个数表示 ( A k ) i , j (A^k)_{i,j} (Ak)i,j,每个元素对 1 0 9 + 7 10^9+7 109+7 取模。
样例 #1
样例输入 #1
2 1
1 1
1 1
样例输出 #1
1 1
1 1
提示
【数据范围】
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 100 1\le n \le 100 1≤n≤100, 0 ≤ k ≤ 1 0 12 0 \le k \le 10^{12} 0≤k≤1012, ∣ A i , j ∣ ≤ 1000 |A_{i,j}| \le 1000 ∣Ai,j∣≤1000。
解题代码
cpp
#include <iostream>
#include <cstring>
using namespace std ;
const int N = 110 ;
const int mod = 1e9 + 7 ;
typedef long long LL ;
struct matrix {
LL m[N][N] ;
};
matrix operator * (const matrix &a , const matrix &b ){
matrix c ;
memset(c.m , 0 , sizeof c.m );
for(int i = 0 ; i < N ; i ++ ){
for(int j = 0 ; j < N ; j ++ ){
for(int k = 0 ; k < N ; k ++ ){
c.m[i][j] += ((a.m[i][k] % mod) *( b.m[k][j] % mod) %mod);
c.m[i][j] %= mod ;
}
}
}
return c ;
}
matrix pow_matrix ( matrix a , LL b ){
matrix c ;
memset(c.m , 0 , sizeof c.m ) ;
for(int i = 0 ; i < N ; i ++ ) c.m[i][i] = 1 ;
while(b){
if(b&1){
c = c * a ;
}
a = a * a ;
b >>= 1 ;
}
return c ;
}
int main () {
LL n , k ;
cin >> n >> k ;
matrix c ;
for(int i = 0 ; i < n ; i ++ ){
for(int j = 0 ; j < n ; j ++ ){
cin >> c.m[i][j] ;
}
}
matrix d = pow_matrix(c , k);
for(int i = 0 ; i < n ; i ++ ){
for(int j = 0 ; j < n ; j ++ ){
cout << d.m[i][j] << ' ';
}
cout << endl ;
}
return 0 ;
}
**Div 2 改装题 **
矩阵加速(数列)
题目描述
已知一个数列 a a a,它满足:
a x = { 1 x ∈ { 1 , 2 , 3 } a x − 1 + a x − 3 x ≥ 4 a_x= \begin{cases} 1 & x \in\{1,2,3\}\\ a_{x-1}+a_{x-3} & x \geq 4 \end{cases} ax={1ax−1+ax−3x∈{1,2,3}x≥4
求 a a a 数列的第 n n n 项对 1 0 9 + 7 10^9+7 109+7 取余的值。
输入格式
第一行一个整数 T T T,表示询问个数。
以下 T T T 行,每行一个正整数 n n n。
输出格式
每行输出一个非负整数表示答案。
样例输入 #1
3
6
8
10
样例输出 #1
4
9
19
提示
- 对于 30 % 30\% 30% 的数据 n ≤ 100 n \leq 100 n≤100;
- 对于 60 % 60\% 60% 的数据 n ≤ 2 × 1 0 7 n \leq2 \times 10^7 n≤2×107;
- 对于 100 % 100\% 100% 的数据 1 ≤ T ≤ 100 1 \leq T \leq 100 1≤T≤100, 1 ≤ n ≤ 2 × 1 0 9 1 \leq n \leq 2 \times 10^9 1≤n≤2×109。
过题代码
cpp
#include <iostream>
#include <cstring>
using namespace std ;
const int N = 5 ;
const int mod = 1e9 + 7 ;
typedef long long LL ;
struct matrix {
LL m[N][N] ;
};
matrix operator * (const matrix &a , const matrix &b ){
matrix c ;
memset(c.m , 0 , sizeof c.m );
for(int i = 0 ; i < N ; i ++ ){
for(int j = 0 ; j < N ; j ++ ){
for(int k = 0 ; k < N ; k ++ ){
c.m[i][j] += ((a.m[i][k] % mod) *( b.m[k][j] % mod) %mod);
c.m[i][j] %= mod ;
}
}
}
return c ;
}
matrix pow_matrix ( matrix a , LL b ){
matrix c ;
memset(c.m , 0 , sizeof c.m ) ;
for(int i = 0 ; i < N ; i ++ ) c.m[i][i] = 1 ;
while(b){
if(b&1){
c = c * a ;
}
a = a * a ;
b >>= 1 ;
}
return c ;
}
int main () {
LL n ;
cin >> n ;
matrix c ;
memset(c.m,0, sizeof c.m);
c.m[0][0] = 1 ;
c.m[0][1] = 1 ;
c.m[0][2] = 1 ;
matrix a ;
memset(a.m,0,sizeof a.m);
a.m[0][1] = 1 ;
a.m[1][2] = 1 ;
a.m[2][0] = 1 ;
a.m[2][2] = 1 ;
while(n--){
LL k ;
cin >> k ;
if(k==1||k==2){
cout << 1 << endl ;
continue ;
}
k -= 3 ;
matrix d = pow_matrix(a,k);
matrix e = c * d ;
cout << e.m[0][2] << endl ;
}
return 0 ;
}