关于数论的一些入门小知识
目录
- 欧几里得算法和扩欧
- 素数相关算法
- 快速幂和矩阵快速幂
PART1 欧几里得及其扩展
关于欧几里得相信大家都听说过了,欧几里得算法是求两个数的最大公约数的算法,具体的公式就是gcd(a,b)=gcd(b,a%b),具体证明戳这里,下面给出代码
int gcd (int a,int b) {
if (b==0) return a;
return gcd(b,a%b);
}
下面我们介绍欧几里得算法的扩展,用来求解ax+by=1这种的丢番图不定方程,首先我们基于上面的证明,我们可以知道对于ax+by=1,一定存在ax+by=gcd(a,b),这个的证明很简单,就不赘述了。下面给出扩欧的证明,bx2+(a%b)y2=gcd(b,a%b),因为:gcd(a,b)=gcd(b,a%b),then: ax1+by2=bx2+(a%b)y2.,我们把a%b用a-(a/b)*b代替,容易化简,最后两个多项式进行比较,就可以得出x和y的迭代公式。下面给出代码:
#include<bits/stdc++.h>
using namespace std;
int exgcd (int a,int b,int &x,int &y) {
if (!b) {
x=1;
y=0;
return a;
}
int d = exgcd(b, a%b, x, y);
int t = x;
x = y;
y = t - (a/b)*y;
return d;
}
int main () {
int x, y;
exgcd(252,198,x,y);
cout<<x<<" "<<y<<endl;
return 0;
}
PART2 素数筛法
我们这里主要介绍两种筛法,埃氏筛和欧拉筛。首先让我们来想一下,朴素的素数判定是怎么操作的,根据素数的性质我们枚举区间内的所有整数,判断能否整除就可以了,这个基础上我们改进算法只判断区间右端点二分之一次方的端点区间,可以得到根号的时间复杂度,对应一个素数的判定,这样的方法已经够用了,但是对于多个素数或者说区间素数的要求来说,我们可以使用埃氏筛这个高效的算法,它的核心思想就是在我确定一个素数的情况下,我可以把这个区间内这个素数的倍数全部筛掉,这个数肯定不是素数。时间复杂度可以看做线性,有兴趣的可以去证一下。下面给出代码:
int solve(int n)
{
int p=0;
for (int i=0;i<=n;i++) prime[i]=1;
prime[0]=prime[1]=0;
for (int i=2;i<=n;i++) {
if (prime[i]) {
prime[p++] = i;
for (int j=2*i;j<=n;j+=i) prime[j]=0;
}
}
return p;
}
关于埃氏筛法,我们会发现,这个方法仍然不够完美,仔细观察我们会发现,在被筛去的数里面,有部分数是会被重复筛去的,比如30会被5筛走一次,也会被2筛走一次,这样我们就会造成浪费,使用我们这里介绍欧拉筛来优化埃氏筛。既然关键在于会被重复筛去,一个被筛去的数必定是被其最小质因子所筛去,使用我们保存所有素数,在访问一个数的时候,遍历一下已经存下的素数,如何两者可以整除,那么我就退出筛法,所以欧拉筛的核心代码是:if(i%prime[j]==0) break;,时间复杂度为线性,下面给出完整的代码。
const int MAXN=3000001;
int prime[MAXN];//保存素数
bool vis[MAXN];//初始化
int Prime(int n)
{
int cnt=0;
memset(vis,0,sizeof(vis));
for(int i=2;i<n;i++)
{
if(!vis[i])
prime[cnt++]=i;
for(int j=0;j<cnt&&i*prime[j]<n;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)//关键
break;
}
}
return cnt;//返回小于n的素数的个数
}
PART3 快速幂
当我们做幂乘的时候,往往会因为数量过大而导致算法跑起来十分缓慢,所以我们引入快速幂的概念,快速幂算法是基于一种倍增的思路,当我每次幂乘一次后,将底数翻倍,乘数减倍那么我们的运算次数就会大大减少,这种思路和我们学的二分十分相似,代码实现方面并不难。基于我们这种思路,我们同样可以实现矩阵的快速运算,相对于是快速幂的扩展,这一算法十分重要,在线性代数的计算和关于求斐波那契数列一系列问题都有运用,下面给出代码:
#include<bits/stdc++.h>
#define MEM(x, y) memset(x, y, sizeof(x))
#define ll long long
using namespace std;
const int maxn=100010;
const int INF=1000000000;
const int mod=1000000007;
const double eps=1e-6;
const double PI=acos(-1.0);
int read() {
int p=0,f=1;
char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
while (c>='0'&&c<='9') {p=p*10+c-'0'; c=getchar(); }
return f*p;
}
long long n,m;
struct maze {
long long c[105][105];
}a;
maze muti (maze x,maze y) {
maze t;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) {
t.c[i][j]=0;
for (int k=1;k<=n;k++) {
t.c[i][j]=(t.c[i][j]+x.c[i][k]*y.c[k][j])%mod;
}
}
return t;
}
maze fastmaze (maze a,long long k) {
maze res=a,b=a;
k--;
while (k) {
if (k&1) res=muti(b,res);
b=muti(b,b);
k=k>>1;
}
return res;
}
int main( ) {
cin>>n;
cin>>m;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) scanf("%lld",&a.c[i][j]);
a=fastmaze(a,m);
for (int i=1;i<=n;i++) {
for (int j=1;j<=n;j++) cout<<a.c[i][j]<<" ";
cout<<endl;
}
return 0;
}