0基础速通基本数论
卷王xy在CF1581的一道800分题中用了逆元,导致我被迫复习数学..
从老博客搬运过来...
upd: 2022.8.28-2022.12.*
随便改一下居然有这么多问题,以前学了个毛..
素数筛法
不超过N的质数大约有 个,即每 个数中大约有一个质数
给定一个整数N,求出1-N中的所有质数,称为质数筛选问题
埃筛
对于任意整数x,它的倍数2x,3x,...都不是质数,我们可以从2开始扫描,将扫描的数标记为合数,当扫描到一个数时,如果它没有被标记,那么这个数就是质数
容易发现6会被2和3重复标记,再次发现小于的x的倍数在扫描更小的数的时候就已经被标记过了,因此有这么一个小优化
void primes(int n) {
memset(v, 0, sizeof(v));
for (int i=2; i<=n; i++) {
if (v[i]) continue;
prime[++length]=i;
for (int j=i; j<=n/i;j++) v[i*j]=1;
}
}
埃筛的复杂度可以达到
然而我们就会发现,还是过不了被修正并加强数据过的线性筛素数
线性筛
即使在优化后,埃筛还是会重复标记合数,根本原因是我们没有确定出唯一产生12的方式,例如12又会被2标记又会被3标记
线性筛通过“从小到大累积质因子”的方式标记每个合数,即让 12 只有 的方式产生,设数组 记录每个数的最小质因子,我们按照以下步骤维护
- 依次考虑 之间的数
- 若 没有被标记,则 为质数
- 扫描不大于 的每个质数 ,令 ,也就是在 的基础上积累一个质因子 。因为 ,所以 就是合数 的最小质因子
换一个角度来看,我们并不用保存具体的v,考虑更新的顺序,如果 ,那么最小因子就不会大于
const int N = 1e7;
int mnp[N + 10];
vector<int> primes;
void pre() {
for (int i = 2; i <= N; ++i) {
if (!mnp[i]) primes.push_back(i), mnp[i] = i;
for (auto &p : primes) {
if (p * i >= N) break;
mnp[p * i] = p;
if (i % p == 0) break;
}
}
}
不需要每个数的最小质因子的话可以用bitset省空间
const int N = 1e8 + 10;
std::vector<int> primes;
std::bitset<N> vis;
void pre(int n) {
for (int i = 2; i <= n; ++i) {
if (!vis[i]) primes.push_back(i);
for (int j : primes) {
if (i * j > n) break;
vis[i * j] = 1;
if (i % j == 0) break;
}
}
}
GCD
已知两个数 求最大公约数
辗转相减
假设 ,则有 ,不断地利用大的数减去小的数,就能的得到最大公约数
证明:
不妨设为的最大公约数,有 ,
显然对于 的任意公约数, 即 变化的只是两者不同的系数,且这两个系数很容易得出互质的结论,最后当一个数变为 时,剩下的数字就会是g,于是我们便得到了最大公约数
while (a!=b) {
a > b ? a-=b : b-=a;
}
辗转相除-欧几里得
我们一般说的GCD都是指欧几里得辗转相除法
在上述过程中 发现一直减的过程就是%
ll gcd(ll a, ll b) {
return b ? gcd(b, a % b) : a;
}
另一种证明:
不妨设 ,, 显然,对于a,b的任意公约数d,因为d|a,d|b,所以d|(a-q*b),因此d也是b,r的公约数
换而言之令,易得,因此的公约数集合与的公约数集合相同,最大公约数自然也相同
例题-NOIP2009 Hankson的趣味题
给定 n 个询问,每个询问给定四个自然数 a,b,c,d ,然后求 x 的数量,其中 且
有一个很奇怪的基本0思维难度以至于都不会选择的方法可以直接过掉
判断b1的所有约数,统计答案,居然能过???
这个的复杂度在的算法,本来只应该有90分,居然能过题
#include<bits/stdc++.h>
#define int long long
inline int read() {
int x=0,f=1; char ch=getchar();
while (!isdigit(ch)) f=(ch=='-')?-1:1,ch=getchar();
while (isdigit(ch)) x=x*10+(ch-'0'),ch=getchar();
return x*f;
}
int gcd(int a,int b) {
return b?gcd(b,a%b):a;
}
int lcm(int a,int b) {
return a*b/gcd(a,b);
}
signed main() {
int n; n=read();
int a0,a1,b0,b1,x,ans,t;
while (n--) {
ans=0;
a0=read(),a1=read(),b0=read(),b1=read();
for (int i=1;i*i<b1;i++) {
if(b1%i==0) {
t=b1/i;
if(gcd(a0,i)==a1&&lcm(b0,i)==b1)
ans++;
if(gcd(a0,t)==a1&&lcm(b0,t)==b1)
ans++;
}
}
int tem=sqrt(b1);
if (tem*tem==b1)
if(gcd(a0,tem)==a1&&lcm(b0,tem)==b1)
ans++;
printf("%d\n",ans);
}
}
看了一圈貌似大部分都这么写,严格的来讲不打一个素数表是过不了的
exgcd
扩展欧几里得算法,常用于求 的一组可行解
根的存在性定理
其实通过辗转相减就可以观察的比较明显
辗转相减的时候相当于 中 不断变化的过程,我们知道这个过程一定可以得到最大公约数,所以 这个方程是肯定有解的
那么,当 为 的倍数时,该方程有解,而且我们可以两边同时除以一个最大公约数得到一个新的式子:
其中 互质,因此 肯定有解,所以 在 整除 时有解,这就是裴蜀定理
特解和通解
所以我们只要求解
然后扩大倍即可
观察这个式子,用辗转相减去理解,假设已经求出了
的解 ,那么变换一下得到 ,可以得到
对于辗转相除也是同理,原问题就变成了求解
又
则有
整理得到
因此我们只要一直缩小问题规模,一直得到,得到一组特解,再将这组特解反推回去就知道原方程的一组特解了
int exgcd(int a, int b, int &x, int &y) {
if (b) {
int g = exgcd(b, a % b, x, y);
x -= a / b * y;
swap(x, y);
return g;
} else {
x = 1, y = 0;
return a;
}
}
函数返回得到的(x,y)是方程的一组特解,通解的变化规律就是x和y的值往相反的方向变化,设这个变化的最小单位量是
两边同除令,这个时候显然互质,即
可得通解为,即
欧拉函数
定义
欧拉函数 表示 中与 互质的个数,例如, (p为质数)
计算式
首先考虑一个质数 ,先来证一下结论:
对于质数 而言,中与 不互质的数有
个,用总数减去 的倍数,就是
容易发现,容斥一下也就是总数- 的倍数-
的倍数,再加上 的倍数
若此质因数分解之后,,有
这就是欧拉函数的积性性质
积性函数:
若 互质,有 ,则称函数为积性函数
根据计算式,只要分解质因数,即可顺便求出欧拉函数
int phi(int n) {
int ans = n;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
ans = ans / i * (i - 1);
while (n % i == 0) n /= i;
}
}
if (n > 1) ans = ans / n * (n - 1);
return ans;
}
欧拉函数的性质
- 若 ,则有
- 当 为奇数时,
- ,前提是 ,可以用计算式推导,证明不难
- ,前提是互质,且是质数,积性函数,证明不难
性质5证明:设,若互质
所以是积性函数
因此
即
线性计算
利用性质,很容易在欧拉筛的时候把 求出来
#include <bits/stdc++.h>
using namespace std;
const int N = 1e7;
bitset<N + 10> vis;
vector<int> pr;
void euler(int n, vector<int> &phi) {
phi[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!vis[i])
pr.push_back(i), phi[i] = i - 1;
for (int &p : pr) {
if (p * i > n) break;
vis[i * p] = 1;
if (i % p == 0) { phi[i * p] = phi[i] * p; break; }
phi[i * p] = phi[i] * (p - 1);
}
}
}
同余
定义
若整数 和整数 除以正整数 的余数相等,则称 模 同余,记为
同余类和剩余系
对于 ,集合 的所有数模m同余,余数都是 ,该集合称为一个模 的同余类,记为
模m的同余类一共有 个,分别为,他们构成 的完全剩余系
中与 互质的数代表的同余类一共有个,它们构成
的简化剩余系
简化剩余系关于模 乘法封闭。这是因为若 与 互质,则 也不可能与m含有相同的质因子,即 也与 互质,即 也属于 的简化剩余系
费马小定理
若p是质数,则对于任意整数a,有
欧拉定理
若正整数 互质,则
证明
设n的简化剩余系为。对
若
则
因为a,n互质,所以,即,故当时,,也代表了不同的同余类
又因为简化剩余系关于模n乘法封闭,故也在简化剩余系的集合中,因此,集合与集合都能表示n的简化剩余系
综上所述
所以
当p是质数时,,并且只有p的倍数与p不互质。所以,只要a不是p的倍数,就有,两边同乘a就是费马小定理,另外若a是p的倍数,费马小定理显然成立
扩展欧拉定理
- ,则有
若正整数a,n互质,则对于任意正整数b,有
证明:
设,其中,即
于是有:
指数循环节
- 当a,n不一定互质且b时,有
详细证明移步 OI-wiki
线性同余方程
给定正数 ,求一个整数 满足 ,或者给出无解。因为未知数的指数为1,所以我们称之为一次同余方程,也称线性同余方程
,等价于 是 的倍数,不妨设为
倍,于是,该方程可以改写成 。
特解和通解就可以用exgcd的部分去套了。
NOIP2012 同余方程
求关于的同余方程 的最小正整数解。
#include<bits/stdc++.h>
void exgcd(int a,int b,int &x,int &y){
if (b) {
exgcd(b,a%b,y,x);
y-=a/b*x;
}
else
x=1, y=0;
}
int main() {
int a,b,x,y;
scanf("%d%d",&a,&b);
exgcd(a,b,x,y);
printf("%d",((x%b)+b)%b);
return 0;
}
模意义下的乘法逆元
扩欧求解单一逆元
一些题里面,我们可能会碰到含除法又含取模的运算,但除法对模运算不封闭,所以要考虑求解一个东西
即 ,其中 是一个常数,合并同类项
那么就有 ,我们称求得的 作 模 意义下的逆元,记为
做一个exgcd就能够实现单个数求逆元,也就是上面那道模板,并且逆元并不总是存在,要求
快速幂求解单一逆元
利用费马小定理求解
可得
然后就可以快速幂 求解,要求 是一个素数
线性求逆元
求 每个数模 的逆元
首先有
然后来尝试推导 ,令 ,则有
即 ,两边同乘 ,可得
,即
这样就可以递推了
用 来避免负数
线性求非连续数的逆元
对于任意 个数 ,计算前缀积 ,然后求出逆元,记为 ,然后倒过来依次乘 ,得到
那么
总体复杂度
模板题,输入要卡常
整除分块
这个东西 atcoder 甚至出过原题
狄利克雷卷积
Dilichlet Convolution
定义在数论函数间的一种二元运算
一些常用的数论函数
莫比乌斯反演
定义:
常数函数 的 Dirichlet Inverse,我们将其称为莫比乌斯函数
素数 ,有
即
其中 都是质数
莫比乌斯反演公式
,卷积形式表示为
线性筛 求
bitset<N> vis;
int mu[N];
vector<int> primes;
void init() {
mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (vis[i] == 0) {
primes.push_back(i);
mu[i] = -1;
}
for (auto p : primes) {
if (p * i > n)
break;
vis[p * i] = 1;
if (p % i == 0) {
mu[i] = 0;
break;
} else
mu[p * i] = mu[p] * mu[i];
}
}
}
性质
满足
即
证明:
设 ,
有
当 时,,值为 ,其余为
二项式定理: