数论整理
本文是作者学的数论里所有的东西。
〇、基本数论
0.基本知识
取余(%):\(a\%b\)表示a除以b的余数。
举几个例子:
\(6\%3=0\\7\%3=1\\8\%3=2\\9\%3=0\)
1.因数和倍数
小学五年级就学过了……a是b的倍数,写作\(b|a\)
2.质数和合数
还是小学五年级……因数只有1和它本身的数,叫做质数。
质数筛法(C++):
①朴素算法
所谓朴素算法,就是暴力。
bool prime(int n){//0表示不是质数,1表示是
if(n<2) return 0;//n是要判断的数,如果是1或者0不是质数
for(int i=2;i*i<=n;i++){//寻找因数,如果不是质数在[2,sqrt(n)]中间肯定有
if(n%i==0) return 0;//如果找到非1和本身的其它因数,说明不是质数
}
return 1;//最后他肯定是质数
}
②埃氏筛
全名埃拉托色尼筛法(对,就是算出地球周长的那位全才发明的)。
我们知道,只要一个数是另一个数(不是1或者那个数)的倍数,那么这个数就不是质数。
所以我们只要定一个数组,表示是不是质数就好了。
对于每一个质数,在\(N\)以内他所有的倍数都标记为非质数。
bool isprime[20005];//记录是不是质数
void prime(){
memset(isprime,1,sizeof(isprime));//默认都是
isprime[0]=isprime[1]=0;//特例
for(int i=2;i<=20000;i++){//这里i从2到几因题而异
if(isprime[i]){//如果这个数是质数
for(int j=i*2;j<=20000;j+=i){//找他所有的倍数
isprime[j]=0;//标记为非质数
}
}
}
}
③线性筛
空间是埃氏筛的两倍。
我们开一个数组prime,表示质数都有哪些。
之后对于每一个数:
如果他是质数就把他加入prime。
遍历prime数组,所有当前数的prime[j]倍都标记为非质数。
bool isPrime[100000010];
int Prime[6000010],cnt=0;//cnt表示prime数组的长度
void GetPrime(int n){
memset(isPrime,1,sizeof(isPrime));//默认为1
isPrime[1]=0;//特例
for(int i=2;i<=n;i++){
if(isPrime[i]) Prime[++cnt]=i;//找到一个质数
for(int j=1;j<=cnt&&i*Prime[j]<=n;j++){//如果i*Prime[j]超出范围,就可以跳过
isPrime[i*Prime[j]]=0;//i的prime[j]倍
if(i%Prime[j]==0) break;//说明后面的这些都已经被筛过了,跳过
}
}
}
3.质因数分解
利用试除法,只要找到一个可以除的质数,就一除到底。
for(int i=2;i*i<=n;i++){//遍历所有可能的约数
while(n%i==0){//只要有一个约数
n/=i;//就要除以它
cout<<i<<" ";//并输出
}
}
if(n>1) cout<<n<<endl;//最后可能有残余
一、gcd
辗转相除法:\(\gcd(a,b)=\gcd(b,a\%b)\) 若\(b=0\)则为\(a\)
更相减损法:\(\gcd(a,b)=\gcd(b,a-b)\) 若\(b=0\)则为\(a\)
注:\(\text{lcm}(a,b)=\frac{a\times b}{\gcd(a,b)}\)
二、排列组合
1.排列
\(A_n^m=\frac{n!}{(n-m)!}\)
2.组合
\(C_n^m=\frac{A(n,m)}{m!}=\frac{n!}{(n-m)!m!}\)
引申(Lucas定理):
\(C_n^m \equiv C_{n\%p}^{m\%p}\times C_{n/p}^{m/p} \mod p\)
三、整除分块
见我的另一篇博客。
四、欧拉函数
\(\phi(n)\)表示\(n\)以内与\(n\)互质的数个数。多个数可以用线性筛。
单个数计算方法:
inline long long phi(long long x){//求phi(x)
long long res=x;
for(long long i=2;i*i<=x;i++){
if(!(x%i)) res=res/i*(i-1);//phi函数的计算规则
while(!(x%i)) x/=i; //除干净
}
if(x>1) res=res/x*(x-1);//和下面的质因数分解一个道理
return res;
}
五、快速幂
1.数太大了怎么办
我的一篇题解
虽然说我们有取余,但是结果还是可能太大,就只能二进制拆分了。
2.幂
我们应该知道\(a^b\)表示\(b\)个\(a\)相乘的结果。
但是,当b太大了怎么办呢?
比如,\(1\leq b\leq 10^{18}\)?
聪明的你肯定会想到和上面那个一样,用二进制拆分。(如果不用二进制拆分我为啥要讲上面那个(((
首先,我的ans初始值是1。
其次,我们m是奇数的时候应该是乘法而不是加法。
最后,\(a\)应该变成\(a\times a\mod p\)。
代码:
#include<iostream>
using namespace std;
int main(){
long long n,m,p,ans=1;
cin>>n>>m>>p;//n的m次方模p
n%=p;//首先取余,防止后面溢出(不加也一样)
while(m){//只要还能除下去
if(m&1) ans=(ans*n)%p;//乘上一个
n=n*n%p,m>>=1;//为下一次做准备
}
cout<<ans<<endl;//没了
return 0;
}
这个东西非常重要,需要极其熟练,在下面的矩阵也会用到。
六、矩阵
1.矩阵的定义
在数学中,矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合 [1] ,最早来自于方程组的系数及常数所构成的方阵。这一概念由19世纪英国数学家凯利首先提出。
矩阵是高等代数学中的常见工具,也常见于统计分析等应用数学学科中。 [2] 在物理学中,矩阵于电路学、力学、光学和量子物理中都有应用;计算机科学中,三维动画制作也需要用到矩阵。 矩阵的运算是数值分析领域的重要问题。将矩阵分解为简单矩阵的组合可以在理论和实际应用上简化矩阵的运算。对一些应用广泛而形式特殊的矩阵,例如稀疏矩阵和准对角矩阵,有特定的快速运算算法。关于矩阵相关理论的发展和应用,请参考《矩阵理论》。在天体物理、量子力学等领域,也会出现无穷维的矩阵,是矩阵的一种推广。
数值分析的主要分支致力于开发矩阵计算的有效算法,这是一个已持续几个世纪以来的课题,是一个不断扩大的研究领域。 矩阵分解方法简化了理论和实际的计算。 针对特定矩阵结构(如稀疏矩阵和近角矩阵)定制的算法在有限元方法和其他计算中加快了计算。 无限矩阵发生在行星理论和原子理论中。 无限矩阵的一个简单例子是代表一个函数的泰勒级数的导数算子的矩阵。
说白了,矩阵就是一个用数填充的矩形。比如:
注意:矩阵分行列
重点:单位矩阵
这个东西,左上右下的这条对角线上都是1,其他地方是0。
单位矩阵一定是正方形的,你不能在数中间填一个数吧(汗
单位矩阵乘任何矩阵都还是它本身,就像1一样。
2.矩阵的加法
加法只限于两个同样大小的矩阵。
举个栗子:
毫无难度~
3.矩阵的转置
输入\(a_{ij}\),输出\(a_{ji}\)。
懂?
#include<iostream>
using namespace std;
int a[105][105],b;
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j];
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++) cout<<a[j][i]<<" ";
cout<<endl;
}
return 0;
}
4.矩阵乘法
前方高能,非战斗人员请撤离!(其实挺简单的,就是\(\LaTeX\)不好画)
呕!为什么一个普通的仅仅是\(4\times 4\)的方格都能这么复杂?!
仔细观察我们会发现,作为结果的\(c_{ij}\),是他这一行的所有a和他这一列的所有b相乘。写成代码就是:
void cheng(){//假设已经有a和b两个矩阵,长度分别为n*m和m*k,结果是n*k的矩阵c均已定义
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){//当前一个方格
c[i][j]=0;//初始化
for(int l=1;l<=m;l++) c[i][j]+=a[i][l]*b[l][j];//这一行的乘上这一列的
}
}
}
5.矩阵快速幂
如果我们需要求矩阵\(a\)的\(b\)次方,该怎么办呢?
我们就用前面的快速幂来写,不过这里我们选择用结构体+重载运算符。
重载运算符的话见下(其他地方可以说是不变):
struct jz{//这是2*2的矩阵快速幂演示
ll a[5][5];//矩阵
jz operator *(jz h){//乘法
jz ans;
for(int i=1;i<=2;i++){
for(int j=1;j<=2;j++){
ans.a[i][j]=0;
for(int k=1;k<=2;k++) ans.a[i][j]+=a[i][k]*h.a[k][j];//乘法模板
}
}
return ans;
}
jz operator %(ll t){//取余操作
jz s;
s.a[1][1]=a[1][1]%t;
s.a[1][2]=a[1][2]%t;
s.a[2][1]=a[2][1]%t;
s.a[2][2]=a[2][2]%t;
return s;
}
};
jz po(jz n,ll m){//快速幂
jz sum;
sum.a[1][1]=sum.a[2][2]=1;//单位矩阵
sum.a[2][1]=sum.a[1][2]=0;
while(m){
if(m%2==1) sum=(sum*n)%p;//跟原版一样的
n=(n*n)%p;
m/=2ll;
}
return sum;
}
需要注意的就是重载的运算符要有返回值才行。
写了这么多,也总结了大部分的数论前段知识(
以后我还会有更多更新,敬请期待!
你也看了这么久,点个赞吧~

浙公网安备 33010602011771号