数论
目录(点击跳转)
欧几里得算法(辗转相除法) 求最大公约数
C++的
库有 __gcd()函数,赛时推荐直接用这个。博文里讲讲原理和实现
原理是:两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数
用递归来实现
int gcd(int a,int b){
return b==0 ? a : gcd(b,a%b);
}
最小公倍数
先求最大公约数gcd(a,b)
int gcd(int a,int b){
return b==0 ? a : gcd(b,a%b);
}
最小公倍数 \(lcm(a,b)\) 与其的关系为:
因此
int lcm(int a,int b){
return a/gcd(a,b)*b;
}
素数筛
试除法
设$ n=a \times b \(,有\) min (a, b) \leq \sqrt{n} \(,令\) a\leq b \(,只要检查\) {[2, \sqrt{n}]} \(内的数,如果\) n \(不是素数,就能找到一个\) a \(。试除法的复杂度是\) O(\sqrt{n}) \(,对于\) n \leq 10^{12} $的数是没有问题的。
bool is_prime(int n){
if(n<=1) return false; //1不是素数
for(int i=2;i*i<=n;i++) //比这样写更好:for(int i=0;i<=sqrt(n);i++)
if(n%i==0) return false;//能整除,不是素数
return true;
}
埃拉托斯特尼筛法(埃氏筛)
从2开始,不断筛掉2的倍数,3的倍数,5的倍数,7的倍数,11的倍数......
埃氏筛简单易用,其时间复杂度为$ O\left(n \log \log _2 n)\right. \(,近似于\) O(n) $;其空间复杂度:当MAXN=1e7时约为10MB
const int MAXN = 1e7+5; //约10MB
vector <int> prime; //存放素数
bool Visit[MAXN]; //true表示被筛掉,不是素数
void sushu(int n){
for(int i=2;i*i<=n;i++){ //筛掉非素数
if(!Visit[i])
for(int j=i*i;j<=n;j+=i)
Visit[j]=true; //标记为非素数
}
for(int i=2;i<=n;i++)
if(!Visit[i])
prime.push_back(i); //存储素数
}
欧拉筛(Sieve of Euler)
欧拉筛是一种线性筛,时间复杂度为 \(O(n)\) ,求得 \(1~n\) 内所有素数。欧拉筛是对埃氏筛的改进,可以避免对很多数的重复判定。
欧拉筛原理:
一个合数肯定有一个最小质因数;让每个合数只被它的最小质因数筛选一次,以达到不重复筛的目的。
欧拉筛可以处理约 \(n=1e8\) 的问题,bool vis[N]约100MB,因为N=1e8时有5761455个素数,因此int prime[5800000],约23MB,否则大小为N就会超出限制
const int N = 1e8;
int prime[5800000]={0};
bool vis[N]={0};
int euler_sieve(int n){
int cnt = 0; //记录素数个数
for(int i=2;i<=n;i++){
if(!vis[i]) prime[cnt++] = i; //如果没被筛过,是素数,记录
for(int j=0;j<cnt;j++){ //用已得到的素数去筛后面的数
if(i*prime[j] > n) break; //只筛<=n的数
vis[i*prime[j]] = 1; //关键1:用x的最小质因数筛去x
if(i%prime[j] == 0) break; //关键2:如果不是这个数的最小质因数,打断
}
}
return cnt;
}
线性丢番图方程
方程$ ax + by = c \(称为二元线性丢番图方程,其中\) a、b、c \(为已知数,\) x、y $为未知量,问是否有整数解。
$ ax + by = c $实际上是二维x-y平面内的一条直线,这条直线上如果有整数点,那么方程就有解,且有无穷多个整数点(解);这条直线上没有整数点,那么方程就没有整数解。
$ ax + by = c \(有解的充分必要条件是\) d = \gcd(a, b) \(能整数\) c $
因为:
设$ a = d \cdot a' \(,\) b = d \cdot b' \(,其中\) \gcd(a', b') = 1 $
那么方程等价于
因此$ c \(必须是\) d $的整数倍才有解
如果$ (x_0, y_0) $是一个特解,那么所有解(通解)可以表示为
因为:
$ (x_0, y_0) \(是一个格点(整数解的点),移动到另一个格点\)
(x_0 + \Delta x ,y_0 + \Delta y)
$,
有$ a \Delta x + b \Delta y = 0 \(,最小的整数解为\) \Delta x = b/d \(,\) \Delta y = -a/d $
扩展欧几里得算法 与 二元丢番图方程的解
求解方程$ ax + by = c \(的关键是找到一个特解\) (x_0, y_0) $,
用扩展欧几里得算法求一个特解$ (x_0, y_0) $的代码如下:
//返回 d = gcd(a,b); 并返回 ax + by = c的特解x,y
typedef long long ll;
ll extend_gcd(ll a,ll b,ll &x,ll &y){
if(b == 0){x = 1; y = 0; return a;}
ll d = extend_gcd(b,a%b,y,x);
y -= a/b * x;
return d;
}
该算法很高效,为$ (O_{log\ min(a,b)}) $
例题--洛谷P1516青蛙的约会

可以发现,两只青蛙跳的路程取余L是同余的,也就是方程
$ k $即为所求。变化方程为
再将其变化为
令$ W = (n-m) \(,\) S = (x-y) $
方程就变化为
这就是一个二元方程了,要做的就是求出最小解$ k_{min} $
用exgcd解,方程就转化成
求出的$ k_j $就是一个特解
所有解可以表示为
这个方程对于$ c∈Z $而言,想通过特解推出一个最小解,可以这样做
而因为这个 $ k $ 是建立在 exgcd 得出的方程上的,方程右边是$ gcd(W,L) \(而不是\) S \(。所以最后我们需要将结果\) ×\frac{S}{gcd(W,L)} $
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll exgcd(ll a,ll b,ll &x,ll &y){
if(b == 0) {x = 1; y = 0; return a;}
int d = exgcd(b,a%b,y,x);
y -= a/b *x;
return d;
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
ll x,y,m,n,l;
cin >> x >> y >> m >> n >> l;
ll k,t, b = n-m, a = x-y;
if(b < 0){
b = -b;
a = -a;
}
int d = exgcd(abs(m-n),l,k,t);
if(a % d !=0) cout << "Impossible\n";
else cout << (k*a/d % (l/d) + (l/d))%(l/d) << '\n';
return 0;
}
裴蜀定理(贝祖定理)
如果$ a \(与\) b \(均为整数,则有整数\) x \(和\) y \(使得\) ax+by=gcd(a,b) $
推论
整数$ a \(与\) b \(互素当且仅当存在整数\) x \(和\) y \(,使得\) ax+by=1 $
一道例题--洛谷P4549

n = 1时,答案就是输入的数
n > 1时,先看前两个数$ A_1X_1+A_2X_2=C \(,由于\) d = gcd(A_1,A_2)\ | \ C \(,显然这里\) S \(取\) gcd(A_1,A_2) $时最小;后面的数和前面的结果进行gcd即可
注意,当$ A_i<0 $时,gcd可能会返回负数,因此最后一步输出正数即可
#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,num1;
cin >> n >> num1;
for(int i=2;i<=n;i++){
int num2;
cin >> num2;
num1 = __gcd(num1,num2);
}
cout << (num1>0 ? num1:-num1) << '\n';
return 0;
}
pick定理
在一个平面直角坐标系内,如果一个多边形的顶点都是格点,多边形的面积等于边界上格点数的一半加上内部的格点数减1,即$ s = \frac{j}{2} + k - 1 $
同余
$ a≡b\ (mod\ m) \(称为\) a \(和\) b \(模\) m $同余
这里有$ m\ |\ (a-b) $
同余式转等式
若$ a \(和\) b \(是整数,\) m \(是正整数,则\) a≡b\ (mod\ m) \(当且仅当存在整数\) k \(,使得\) a=b+km $。
例如$ 19≡-2(mod\ 7) \(,有\) 19 = -2+3×7 $
模运算性质
这是加法和乘法的模运算。对于除法,也能满足
吗
不满足!举反例a=8,b=2,p=6就不满足
逆Inverse
求解一般形式的同余方程$ ax≡b\ (mod\ m) $,需要用到逆(Inverse)
给定整数$ a \(,且满足\) gcd(a,m) = 1 \(,称\) ax≡1(mod\ m) \(的一个解为\) a \(模\) m \(的逆,记为\) a^{-1} $。
例如,$ 8x≡1\ (mod\ 31) $,丢番图方程:
有一个解是$ x = 4 $,4是8模31的逆,因为4×8-1才能整除31。所有解,如35,66等,都是8模31的逆
扩展欧几里得算法 求单个逆
例题洛谷P1082,求解同余方程$ ax≡1\ (mod\ m) $



$ ax≡1\ (mod\ m) \(,即丢番图方程\) ax +my=1 \(,用扩展欧几里得算法求出一个特解\) x_0 \(,通解就是\) x = x_0 + tm \(。再用\) ((x_0\ mod \ m)+m)\ mod\ m $求出最小正整数解
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
//先用扩展欧几里得方法求出特解
ll exgcd(ll a,ll b,ll &x,ll &y){
if(b == 0) {x = 1; y = 0; return a;}
ll d = exgcd(b,a%b,y,x);
y -= a/b * x;
return d;
}
ll inv(ll a,ll m){
ll x,y;
exgcd(a,m,x,y);
return (x%m + m) % m; //因为要最小正整数
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
ll a,b;
cin >> a >> b;
cout << inv(a,b);
return 0;
}
线性求逆
模板题洛谷P3811
首先有$ i = 1 \(的逆是\) 1
\(。然后用递推求\) [1,n] \(内的所有逆,\) O(n) $的时间复杂度
(1) 设$ \frac{p}{i}=k \(,余数是\) r $,即
(2) 在等式两边乘$ i{-1}×r
$,得到
(3) 移项得
代入$ k=\frac{p}{i} $,得
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 3e6+5;
ll inv[maxn]{};
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
ll n,b;
cin >> n >> b;
inv[1] = 1;
cout << inv[1] << '\n';
for(int i=2;i<=n;i++){
inv[i] = ((b-b/i) * inv[b%i])%b;
cout << inv[i] << '\n';
}
return 0;
}

浙公网安备 33010602011771号