数论从白痴到入门
数论
概念基础
注:本文默认 为下取整的除法。
整除
定义:对于两个数 ,我们称 ,当且仅当存在一个 使得 。
这个运算有一些稍微值得被称为性质的性质,如下:
性质1
该运算具有传递性,即 时, 成立。
好像也没什么好说的性质了。
然后对于 的情况下, 称为 的约数, 称为 的倍数。
实际上所有数之间的关系可以用 表示,当 时, 成立。 其中 称为余数。
最大公约数和最小公倍数
最大公约数(gcd)和 最小公倍数(lcm) :字面意思,不多说了。
当 ,那么我们称 互素。(多于两个数的情况也有这样类似的定义)
需要注意,多个数互素不一定他们两两互素。
求解gcd
如果求解 的 ?
不妨钦定 。 的情况答案直接是 ,不考虑了,我们只考虑 的情况。
我们证明 成立即可,证明如下:
设 ,显然有 。我们取 满足 ,显然 为 公约数。
现在我们证明 : ,那么 ,显然 为整数,那么 。这是必要性。
充分性同理不再赘述。
所以 成立。Q.E.D.
那么之后递归求解即可。当 时,此时的 就是最大公因数。(因为是找到的第一个整除的)
复杂度 的。因为每次取模会至少让数折半。
code
int gcd(int x,int y){
if(!x) return y;
return gcd(y%x,x);
}
求解lcm
对于 质因数分解得到,,
显然 ,
由于
那么 ,可以通过算 求解。
质数
没有除 和它本身以外的因数的数叫做质数。
性质1
一个合数 一定存在素数 使得 。
首先合数可以质因数分解且至少有两个质因数,那么结论显然。
性质2
所以大于 的质数都可以写成 。
发现 都不行即可得证。
素数的筛取 欧拉筛
算数基本定理和引理
算数基本引理
素数 时, 和 至少成立一个。
质因数分解证明。
算数基本定理
对于正整数 ,其存在唯一的质因数分解。
证明略证,不为质因数的可以拆成质因数。
同余
,字面意思。这样的我们称 与 同余。这个式子称为同余式。
性质1
具有传递性。简单不证。
性质2
具有线性性和积性。即对于 , 同余, 同余,那么:
转成余数大家就都一样了,得证。
性质3
对于 , ,那么
以及当 时, 成立。
证明:
设 ,那么上述式子只需代入后结论显然。
性质4
对于 , ,那么
证明:
设 ,由于 ,
Q.E.D.
同余的乘法逆元
然后同余存在乘法逆元的概念。,则 ,称为 的乘法逆元。
逆元递推式子证明
首先 显然。
考虑当前求 的逆元。设 ,有 ,那么
直接递推即可。
其实我们通过上式中 这个部分还可以发现只有当 时, 在 意义下才存在逆元。
然后关于另外那些东西为什么正确,我们之后证明。
同余方程
形如 的方程叫做同余方程。
扩展欧几里得
求解 的算法。
令:
则:
于是有
递归求解即可。设置 返回递归。
根据一些写法不同,有些细节也不太一样,请注意。
code
int exgcd(int a,int b,int &x,int &y){
if(!a){
x=0;y=1;
return b;
}
int d=exgcd(b%a,a,x,y);
int tmp=y;
y=x;x=tmp-(b/a)*x;
return d;
}
模板题 参考代码:(去除了头文件)
int exgcd(int a,int b,int &x,int &y){
if(!a){
x=0;y=1;
return b;
}
int d=exgcd(b%a,a,x,y);
int tmp=y;
y=x;x=tmp-(b/a)*x;
return d;
}
inline bool LiEu(int a,int b,int c,int &x,int &y){
int d=exgcd(a,b,x,y);
if(c%d) return 0;
int k=c/d;
x*=k;y*=k;
return d;
}
int main(){
filein(a);fileot(a);
int a,b,c=1,x,y;
read(a,b);
if(int d=CgEu(a,b,c,x,y) ){
int t=b/d;
printf("%d\n",(x%t+t)%t);
}
return 0;
}
定理1(裴蜀定理)
为方程 即 有整数解的充要条件。
或者对于正整数 ,存在 使得
证明:
充分性:
设 为最小的非负数满足 ,令
则
所以 满足 ,且
由于 是满足 的最小的正数,有 ,即
同理可得
令 ,有 且
又因为 ,且 ,得
所以
所以 中 的最小非负取值为
显然对于 都满足
Q.E.D.
必要性:
令
由 有解, ,则 ,即
Q.E.D.
扩展: 对于关于 的不定方程 ,其有解的充要条件是
知道就好,不证了。
我们知道了这个东西,就可以对于方程 ,先求出 满足 ,那么根据倍数关系直接可以得到一组解
然后我们发现对于 ,可知方程的其他解都形如 ,于是方程所有解都可以求得。
然后 就是最小正整数解。
数论函数
我们在之前已经稍微提过了,这里不再赘述。
莫比乌斯反演及数论函数基本概念
筛法
欧拉函数
定义 ,表示小于等于 的数中与 互质的数的个数。
一个显而易见的结论,当 为质数的时候, 。
性质1
欧拉函数是积性函数。
结论在 欧拉筛 已经证明。
性质2
证明:
由于函数积性性,所以我们只考虑 的情况即可。( 是质数)
Q.E.D.
性质3
设 ,有
证明:
Q.E.D.
威尔逊定理(Wilson)
威尔逊定理
对于素数 有
证明这个定理前先证明几个结论。
引理1
对于素数 和集合 中的元素 , 一定存在 且
证明:
反证。
如果 ,且 ,则 必定为 ,矛盾。
如果 ,则 ,然后有 ,则 必为 ,矛盾。
因此结论成立。
Q.E.D.
引理2
对于元素 ( 定义同上),他们模 意义下的逆元互不相同。
证明:
反证。假设存在两个不同的 的逆元 ,那么 ,则 ,因此 ,矛盾。
因此结论成立。
Q.E.D.
接下来证明威尔逊定理。
证明:由引理1,可知
得证
Q.E.D.
费马小定理和欧拉定理
费马小定理
若 为素数,则 ,如果 ,还有
证明:
我们任取一个正整数 , 不为 的倍数。
可以发现 ,因为 是互不相同的(相当于增减 ,但是由于不整除,余数上固定增减一个小于 的数,又由于模数是素数没有约数必然会错位,所以可知)。
则
有
当 不成立时,那个 无法取的,只能得到
欧拉定理
若 ,则 (注意这里区别在于不需要要求 是素数)
证明:
构造一个与 互质的数列,那么证明就和费马小定理的差不多了。
令
设 为一个 的简化剩余系,那么 也是一个 的简化剩余系。
然后有 ,于是
Q.E.D.
发现费马小定理就是其中一个特殊情况。
扩展欧拉定理
不证啦!想了解的可以看 OI_wiki
中国剩余定理
中国剩余定理
求解一元线性同余方程:
其中 要求互质。
我们提出一种古人的做法,然后证明其正确性并加以理解。
- 计算
- 对于第 个方程计算
- 对于第 个方程计算 (在模 意义下)
- 对于第 个方程计算
- 得出方程唯一解
证明一下这个式子的正确性。
证明:
下面的证明中,第二步的理由是 ,第三步的理由是对于 , 。
Q.E.D.
code
inline ll CRT(){
ll P=1;
for(int i=1;i<=n;++i) P*=p[i];
for(int i=1;i<=n;++i){
ll zm=P/p[i],fm,y;
LiEu(zm,p[i],1,fm,y);
ans=(ans+1ll*a[i]*zm%P*fm%P)%P;
}
return ans;
}
扩展中国剩余定理
其实我感觉这个东西和上面的东西没有什么关系了。
先考虑只有两个方程 的情况。
得到不定方程 ,即 。
于是可以 exgcd
求解出一组解 。(当然无解也是可能的)
由于 ,然后我们构造同余方程
现在我们证明方程的通解是对于其中一个解 来说的,有通解 。
证明:
等价于证明 之中只有一个解。
那么我们假设其中有两个解 ,那么他们都满足给出的两个方程,相互作差可以发现 满足 和 ,即 。
由于 ,只有当 ,即 时成立。
那么可知每个模 的完全剩余系中只有一组解。
Q.E.D.
因此可得同余方程 ,多个方程接着以这种方式合并即可。
inline ll mul_low(ll a,ll b,ll p){
ll res=0;bool f=0;
if(b<0) {b=-b;f=1;}
while(b){
if(b&1) res=(res+a+p)%p;
a=(a+a+p)%p;
b>>=1;
}
return f?-res:res;
}
ll exgcd(ll a,ll b,ll &x,ll &y){
if(!a){
x=0;y=1;
return b;
}
ll d=exgcd(b%a,a,x,y);
ll tmp=y;
y=x;x=tmp-(b/a)*x;
return d;
}
inline bool LiEu(ll a,ll b,ll c,ll &x,ll &y){
ll d=exgcd(a,b,x,y);
if(c%d) return 0;
ll k=c/d,t=b/d;
x=mul_low(x,k,t);
return 1;
}
ll a1,a2,m1,m2;
ll gcd(ll a,ll b){
if(!a) return b;
return gcd(b%a,a);
}
inline ll lcm(ll a,ll b){
return (a/gcd(a,b) )*b;
}
int n;
int main(){
filein(a);fileot(a);
read(n);
read(m1,a1);
for(int i=2;i<=n;++i){
read(m2,a2);
ll p,q;
LiEu(m1,m2,a2-a1,p,q);
ll new_m=lcm(m1,m2);
a1=(mul_low(p,m1,new_m)+a1)%new_m;
m1=new_m;
}
printf("%lld\n",a1);
return 0;
}
卢卡斯定理(Lucas)
卢卡斯定理
对于质数 ,有:
证明:
发现 ,只有在 或 的情况下,有 。
那么有
于是:
我们由
可知, 是在第 项的取值。
又由我们前面证明的东西:
然后我们观察第 项:
可以这么构造的原因是 必定要是 的倍数,且 ,所以有且仅有这种构造满足次数和为 。
一个另外的对该公式的理解是 和 在 进制下的每位的组合数的乘积,可以用于一些奇怪的地方。
同余最短路
大多是两种类型的题目:
- 用很多数拼凑一个大数,问是否可以拼成。
- 给一些数的变换操作(有模数限制),问到达的最少步数。
其中第一个本质上是以一个小数作为模数,展开剩余系,然后对于剩余系上每个数存储最低可以到达的与当前数同余的数,比这个数大或等于就可以到达(因为剩余系的模数是可以用来拼数的),否则到达不了。
第二个本质就是最短路,不过由于模数限制会反复到达一些点。
这个部分不那么需要证明的思想,更多用到的是一种图论感觉的思想。
讲两道典型例题:
题意还是挺简单的,就不再简化了。
本质上就是给了三个数 ,问 及之下有多少可以被拼凑出来。
我们取这个三个数的最小值最为模数展开剩余系,然后形如 边权为 的边,跑最短路可以得到最低可达。然后计算一下即可。
题意还是简单,不简化了。
转换一下,对给出的数展开剩余系,可以转换成两种操作: 和 。
可以发现每次 都使得数位累加 。我们将这两种操作视作两条边,然后 边权为 , 边权为 ,然后从 往 跑最短路。
你可能发现连续 到进位的情况是不正确的,但是不用担心,这种操作肯定是不优的,因此不会对答案造成影响。
由于最坏一直 都能到达,复杂度 。
大概就是这样的东西吧。
原根
见 此文 。
二次剩余
如果存在 ,则称 为模 的二次剩余。
通俗来说就是开平方。需要注意的是, 不需要小于 。
先声明 Legendre
记号:
Euler 叛别准则
若奇素数 满足 ,则:
证明略。对于 的原根 ,此时若 为偶数,二次剩余的条件得到满足。
Legendre
记号为完全积性函数。
小于 的正数中二次剩余的数量与非二次剩余数量相等。
但是这个只能判断是否为二次剩余,我们考虑怎么给这玩意开根。
Cipolla算法
考虑求 。
先 Euler判别准则
判无解,再特判 。
如果有解则随意选一个数 开始。这个 需要满足, 为非二次剩余。
这个 我们随机算取,由上面的内容我们知道概率为 。
然后设 ,然后答案就为 。这个 我们视作虚数,因为实际上 是非二次剩余,这个 实际上并不存在实际的意义。
因为我们知道:
第三步的理由是 ,由 Euler判别准则
。
然后直接算就没了。
#include<bits/stdc++.h>
#define ll long long
#define db double
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
#define sky fflush(stdout)
#define gc getchar
#define pc putchar
namespace IO{
template<class T>
inline void read(T &s){
s=0;char ch=gc();bool f=0;
while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
if(ch=='.'){
T p=0.1;ch=gc();
while('0'<=ch&&ch<='9') {s=s+p*(ch^48);p/=10;ch=gc();}
}
s=f?-s:s;
}
template<class T,class ...A>
inline void read(T &s,A &...a){
read(s);read(a...);
}
};
using IO::read;
int n,mods;
inline int inc(int x,int y){
return (x+=y)>=mods?x-mods:x;
}
inline int dec(int x,int y){
return (x-=y)<0?x+mods:x;
}
inline int mul(int x,int y){
return 1ll*x*y%mods;
}
inline int qpow(int a,int b){
int ans=1;
while(b){
if(b&1) ans=mul(ans,a);
a=mul(a,a);
b>>=1;
}
return ans;
}
int I2;
struct Complex{
int a,b;
inline friend Complex operator * (Complex x,Complex y){
return {inc(mul(x.a,y.a),mul(mul(x.b,y.b),I2) ),inc(mul(x.a,y.b),mul(x.b,y.a) )};
}
};
inline Complex qpow(Complex a,int b){
Complex ans={1,0};
while(b){
if(b&1) ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
std::mt19937 rd(time(0)*114+clock()*514);
inline void solve(){
read(n,mods);
if(n==0){
printf("%d\n",0);
return;
}
if(qpow(n,(mods-1)/2)==mods-1){
printf("Hola!\n");
return;
}
while(1){
int a=(rd()%(mods-1) )+1;
I2=dec(mul(a,a),n);
if(qpow(I2,(mods-1)/2)==mods-1){
Complex ans={a,1};
ans=qpow(ans,(mods+1)/2);
int ans1=ans.a,ans2=inc(-ans.a,mods);
if(ans1>ans2) std::swap(ans1,ans2);
printf("%d %d\n",ans1,ans2);
break;
}
}
}
int main(){
file(a);
int T;read(T);
while(T--){
solve();
}
return 0;
}
离散对数
BSGS
解关于 的同余方程
考虑到 为质数,由于 ,我们可以考虑分块,块长根号。
于是 可以表示为 。预处理出 ,然后 一起加入 hash表
。然后预处理出 然后依次查表即可。
其实你也可以写成减的形式,就不需要求逆元。
因为我们要求的是:
#include<bits/stdc++.h>
#define ll long long
#define db double
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
#define sky fflush(stdout)
#define gc getchar
#define pc putchar
namespace IO{
template<class T>
inline void read(T &s){
s=0;char ch=gc();bool f=0;
while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
if(ch=='.'){
T p=0.1;ch=gc();
while('0'<=ch&&ch<='9') {s=s+p*(ch^48);p/=10;ch=gc();}
}
s=f?-s:s;
}
template<class T,class ...A>
inline void read(T &s,A &...a){
read(s);read(a...);
}
};
using IO::read;
const int inf=1e9;
#define int unsigned int
int n,mods,a;
inline int inc(int x,int y){
return (x+=y)>=mods?x-mods:x;
}
inline int dec(int x,int y){
return (x-=y)<0?x+mods:x;
}
inline int mul(int x,int y){
return 1ll*x*y%mods;
}
inline int qpow(int a,int b){
int ans=1;
while(b){
if(b&1) ans=mul(ans,a);
a=mul(a,a);
b>>=1;
}
return ans;
}
template<class T,int MAXN,int MAXM>
struct StarList{
StarList(){
tot=-1;
memset(head,-1,sizeof(head) );
};
int nxt[MAXM],head[MAXN],tot;
T to[MAXM];
inline void link(int u,T v){
nxt[++tot]=head[u];
head[u]=tot;
to[tot]=v;
}
};
int B;
struct HashMap{
#define S (1145141)
struct node{
int x,id;
};
StarList<node,S+3,S+3>h;
inline void ins(int x,int id){
h.link(x%S,{x,id});
}
inline int find(int x){
int mi=inf;
for(int i=h.head[x%S];~i;i=h.nxt[i]){
if(h.to[i].x==x){
mi=std::min(mi,h.to[i].id);
}
}
return mi;
}
}ex;
signed main(){
file(a);
read(mods,a,n);
B=sqrt(mods-1);
int sum=n,inva=qpow(a,mods-2);
for(int i=0;i<B;++i,sum=mul(sum,inva) ){
ex.ins(sum,i);
}
int now=1,aB=qpow(a,B);
for(int i=0;i<=mods-1;i+=B,now=mul(now,aB) ){
int d;
if((d=ex.find(now) )!=inf){
printf("%d\n",i+d);
return 0;
}
}
printf("no solution\n");
return 0;
}
这个做法需要的就是模数与基底互质,不互质的可以除最大公约数扩展。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具