数论(长期更新)
欧几里得算法
求
int gcd(int a,int b){
if(!b) return a;
return gcd(b,a%b);
}
更相损减术与之类似(假如真的要写的话)。
exgcd
求解不定方程
裴蜀定理
逆定理:
设
可推广到
对于解方程来说,有解
找特解/通解
类似辗转相除,最后找到一组特解后回代,得到原来方程的特解。
通解形式也很好找。
其中
主要是一堆分讨和细节。
看代码:
板子
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL t,a,b,c,x,y,x1,yy,dx,dy;
LL gcd(LL a,LL b)
{
if(!b) return a;
return gcd(b,a%b);
}
void exgcd(LL a,LL b,LL &x,LL &y)
{
if(!b)
{
x=1,y=0;
return;
}
exgcd(b,a%b,x,y);
LL t=x;
x=y,y=t-(LL)a/b*y;
}
int main()
{
scanf("%lld",&t);
while(t--)
{
scanf("%lld%lld%lld",&a,&b,&c);
LL d=gcd(a,b);
if(c%d)
{
printf("-1\n");
}
else
{
exgcd(a,b,x,y);
x=c/d*x;
y=c/d*y;
dx=b/d,dy=a/d;
LL k;
if(x<0) k=ceil((1.0-x)/dx),x+=dx*k,y-=dy*k;
if(x>=0) k=(x-1)/dx,x-=dx*k,y+=dy*k;
if(y>0)
{
printf("%lld ",(y-1)/dy+1);
printf("%lld ",x);
printf("%lld ",(y-1)%dy+1);
printf("%lld ",(y-1)/dy*dx+x);
printf("%lld\n",y);
}
else
{
printf("%lld ",x);
printf("%lld\n",y+(LL)ceil((1.0-y)/dy)*dy);
}
}
}
return 0;
}
类欧几里得算法
给定
设:
类欧的主要想法就是把式子中的
首先考虑算
当
然后只需算
对于后面括号中的式子,进行等价的变形:
那么原来的式子就是:
这样递归即可,这里
那么
来算
当
当
为方便,以下令
当
当
将
综上,
边界就是
数论分块
用于和式计算中将含有
可以发现
主要记忆一些结论:
-
使得
成立的最大的 。 -
使得
成立的最大的 ,注意 时特殊处理,否则分母为 。 -
二维/高维形式上,每一维都和一维形式相同,故取
即可。 -
使得
成立的最大的
数论函数
积性函数
若对于
若对于任意的
一般来说,对于积性函数
一些常见的积性函数:
-
单位函数:
,同时是完全积性。 -
恒等函数:
,同时是完全积性。当 时,简记为 。 -
常数函数:
,同时是完全积性。 -
除数函数:
, 即因数个数,简记为 , 即因数之和,简记为 。 -
欧拉函数。
-
莫比乌斯函数。
积性函数的取值由素数幂处的取值决定,于是求积性函数时总是关注其素数幂处的取值。
狄利克雷卷积(Dirichlet卷积)
称
和狄利克雷生成函数相关,对应生成函数的乘法。
上面的莫比乌斯反演式子就可以写成:
这是由莫比乌斯函数的性质得到的:
欧拉函数的性质可以表示为:
狄利克雷卷积的性质:
-
交换律,
。 -
结合律,
。 -
分配律,
。 -
等式的性质:
。 -
单位元:
。 -
逆元:
,则 互为逆元。逆元是唯一的。
重要结论:
-
两个积性函数的卷积还是积性函数。这个可以证明一坨和式是积性的,然后就可以去筛它。
-
一个积性函数的逆还是积性函数。
点乘
定义点乘
性质:当
常用的一些东西:
可以发现两个积性函数的点乘还是积性函数。
欧拉函数
运用下面的
由两个积性函数的狄利克雷卷积还是积性函数,可知
来看其素数幂处的取值,设
那么可以得到单点求
还有常用的性质:
直接用
莫比乌斯函数
初始化时记得
莫比乌斯反演
性质:
证明一下:
显然
于是结合定义,原式即
反演式子:
筛法
埃氏筛
考虑筛素数。一个方法是从
线性筛(欧拉筛)
埃氏筛还是太慢,关注其过程,发现一个数会被它的每个质因子都标记一次。
我们优化这一点,让一个数只会被它的最小质因子标记。
这样是
实现是为人所熟知的。
实现
void init(){
np[0]=np[1]=true;
for(int i=2;i<=lim;++i){
if(!np[i]) p.emplace_back(i);
for(int j=0;j<(int)p.size()&&p[j]*i<=lim;++j){
np[i*p[j]]=true;
if(i%p[j]==0) break;
}
}
}
注意到在线性筛的过程中,我们也得到了一个数
筛积性函数
对于当前枚举的素数
-
,那么由积性函数性质, 。这里的 和 都是已经算过的。 -
,那么这时 是 的最小质因子,这里就要特殊考虑转移了,需要一些精细的观察。 -
特别地,当筛出了一个素数
时, 的取值要特殊算。
还有一种对注意力要求不是很高的方法:
我们设
那么
这里要求对于任意素数
杜教筛
这里不需要积性的良好性质。
对于任意数论函数
先找到另一个数论函数
把右边第一项拿出来,得到
如果
PN筛
Powerful Number
设正整数
性质:
首先,每一个PN都可以表示为
然后,我们可以证明
如何求
Powerful Number筛
即PN筛。
需要积性。
求特定积性函数前缀和:
PN筛要求存在一个函数
-
是积性函数。 -
的前缀和容易求。 -
在质数
处, 。
假设现在已经找到了
我们又构造
在素数
根据
根据
以上注意
现在枚举
算
-
直接推式子找到
的通项。 -
递推一下,根据
可得 ,提出第一项就有 。
复杂度
费马小定理
若
证明不会。
于是当
欧拉定理
若
扩展欧拉定理
乘法逆元
乘法逆元的
-
扩欧:要求
。 -
费马小定理:要求
是素数且 。 -
线性求逆元:
首先,显然
。
我们希望求
,考虑 ,则 。
两侧同乘
,则 ,即 。
由于负数不太好,再加上
,则
- 求任意
个数的逆元:求出前缀积的逆元,再倒着推回去求每个前缀积的逆元,那么每个数的逆元都可以 计算。
同余基本性质
应该都知道吧。
中国剩余定理
求解一元线性同余方程组。
一般的CRT
强调
过程:
-
求
。 -
对于第
个方程,求 以及 -
可以证明这样是对的。
扩展中国剩余定理(exCRT)
考虑合并两个方程
于是用exgcd求出
然后得到通解形式
于是合并得到了
两两合并
Lucas定理和exLucas
Lucas
强调模数
证:
注意到
, 因此
。 对于
, 。 我们现在对
做一点变换, 所以
设
, 则
。 因为
,所以将 拆成 的形式的方法是唯一的,即 。 所以
。 原式得证。
感觉不是很严谨,感性理解一下。
exLucas
和上方的Lucas定理相比,除了用途,其余并无关系。
求解:
其中
首先我们可以对
然后使用CRT把原问题拆了:求出
把
现在还求不了逆元,因为分母可能不与
记
于是现在就是要求
分母上的数现在可以求逆元了,那么现在要求的就是
考虑把
再把两边都除以
右边前面那一坨显然是可以递归下去做的。
后面那一坨我们再拆一下。由于
前后两坨都可以
这样递归下去算,时间复杂度单次询问
原根
需要了解一些性质。
离散对数
主要用大步小步。
BSGS
求解
我们可以设
其中
我们已知
从而我们可以得到所有的
这里要求
进阶应用
求解
可以转化为BSGS。
exBSGS
剩余
主要用二次剩余。
定义:令
Euler判别法
对于奇素数
二次剩余的数量
在模奇素数
Lengendre符号
性质:
对于任意整数
使用原根可以证明。
Cipolla算法
求解
我们先找一个
根据上面的性质,
我们在模
Lemma 1:
证明:
Lemma 2:
证明:用二项式定理展开即可。
我们可以进行计算了:
于是得到一个解为
设存在复数
左边是实数,那么右边必为实数,于是
代码实现需要一个手写复数类。
代码
#include<bits/stdc++.h>
using namespace std;
#define gc getchar
int rd(){
int f=1,r=0;
char ch=gc();
while(!isdigit(ch)){ if(ch=='-') f=-1;ch=gc();}
while(isdigit(ch)){ r=(r<<1)+(r<<3)+(ch^48);ch=gc();}
return f*r;
}
mt19937 rnd(233);
int mo,i_2;
inline int add(int x,int y){
return x+y>=mo?x+y-mo:x+y;
}
inline int sub(int x,int y){
return x-y<0?x-y+mo:x-y;
}
inline int mul(int x,int y){
return 1ll*x*y%mo;
}
struct cpl{
int re,im;
cpl(int _x=0,int _y=0):re(_x),im(_y){}
cpl operator*(const cpl &tmp){
return cpl(add(mul(re,tmp.re),mul(mul(im,tmp.im),i_2)),add(mul(re,tmp.im),mul(im,tmp.re)));
}
};
int ksm(int x,int y){
int rs=1;
while(y){
if(y&1) rs=mul(rs,x);
x=mul(x,x);
y>>=1;
}
return rs;
}
cpl ksm(cpl x,int y){
cpl rs(1,0);
while(y){
if(y&1) rs=rs*x;
x=x*x;
y>>=1;
}
return rs;
}
bool lengendre(int x){
return ksm(x,(mo-1)>>1)==1;
}
int cipolla(int n,int p){
n%=p,mo=p;
if(!n) return 0;
if(!lengendre(n)) return -1;
int a=rnd()%mo;
while(lengendre(sub(mul(a,a),n))) a=rnd()%mo;
i_2=sub(mul(a,a),n);
return ksm(cpl(a,1),(mo+1)>>1).re;
}
int T,n,p;
void solve(){
n=rd(),p=rd();
int rs=cipolla(n,p);
if(!rs) puts("0");
else if(rs==-1) puts("Hola!");
else{
if(rs>p-rs) rs=p-rs;
printf("%d %d\n",rs,p-rs);
}
}
int main(){
T=rd();
while(T--) solve();
return 0;
}
Miller-Rabin素性测试和Pollard Rho分解质因数算法
这是唯一真史
素数
定义都知道。
素数有无限个。欧拉有一个巧妙的证明。
素数定理
设
那么
哥德巴赫猜想
猜想:任意
这个在很大的范围内都是对的,但是还没有证明。
Stern-Brocot Tree和法里级数
Stern-Brocot树
可以构造满足
我们从初始的两个分数
新的分数
这样可以用一个三元组来描述一个位置:
这些分数的阵列可以看成是一棵无限的二叉树构造。
它的顶端看起来像是这样:
来看它的性质。最重要的一个是:如果
我们来归纳证明它。初始状态
当插入一个中位分数时,我们要验证的就是:
这里打开括号后是与原来的条件等价的。于是这个性质总是成立。
有这个东西后由裴蜀定理的逆定理,我们就知道了Stern-Brocot树中的分数总是最简的。
此外,设
这就说明了Stern-Brocot树不会重复。
下面说明不会遗漏任意一个非负分数。
考虑
我们每次生成一个中间分数
蕴含
从而
我们把左边打开就得到了
右边在不断增大,左侧不变,于是在至多
我们可以用矩阵描述在Stern-Brocot树上的行动。
设当前点为
颠倒是为了初始时更加符合直觉:
设当前点的矩阵为
那么可以得到
设
我们还可以观察到,Stern-Brocot树是一棵二叉搜索树,于是我们可以在它上面进行二叉搜索。
以上二者结合,有两种二叉搜索的方式来确定
-
从
开始移动。 -
从
开始移动回到 。
我们还可以发现,移动过程中可能会连续进行很多次
复杂度是
这样就可以快速二分了。
法里级数
阶为
这是Stern-Brocot树的一个子树。
很牛,但是整棵Stern-Brocot树还是更有意义。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】