基础数论知识
前言
基础数论知识。
original edition 2023.3.29。
upd 2023.6.19:为明天听 zyw 大佬讲题复习,并优化 Latex。
upd 2023.6.20:扩展欧几里得,同余最短路,逆元,中国剩余定理及扩展。
upd 2023.6.21:BSGS。
upd 7.2:高斯消元。
upd 10.20: csp-s 考前复习,更正逆元板块一些错误。
知识点
线性筛
- 原理:让每个数被它最小质因数筛一次。
- 若
,则 的最小质因数为 而非 。此时筛完 break。
代码实现:
for(int i=2;i<=n;i++) { if(!v[i]) p[++k]=i; for(int j=1;j<=k&&i*p[j]<=n;j++) { v[i*p[j]]=1; if(i%p[j]==0) break; } }
唯一分解定理
。 正约数个数: 。 正约数和: 。
最大公约数
。 。
证明:
,设 - 设
, 为 公约数集合任一数。则 即 。所以 >和 有相同公约数集合,故最大公约数相同。 tip : 设
,则 ,必然整除
欧拉函数
定义: 中与 互质的数的个数。- 根据唯一分解定理
,
证明:
对于每个质因子
, 中均有 个倍数。 对于每几个质因子(这里举两个)
, 的倍数被减了两次,需要加回来一次,即 (容斥原理)
- 欧拉函数性质(省略版)
(1).
(2). 欧拉函数是积性函数。积性函数性质:
(3). 若
(4). 若
tip:
证明:
(1). 设
与 互质,又 所以与 互质的数 成对出现,平均值为 ,有 个。 (2). 请自行用公式计算证明。
(3). 设
,则 ,显然 质因子相同,代入公式算即可。 (4).
则 ,故 不是 倍数, 又为质数,二者肯定互质,利用积性函数性计算即可。 tip:根据(3),(4),可以线性求欧拉函数(很简单,直接手推一波啥事没有)。
欧拉定理及扩展
- 若正整数
互质,则 。( 为质数你猜猜是啥) - 若正整数
互质,则对于任意正整数 ,有 - 若
不一定互质,且 ,有
tip : 是费马小定理。
证明:
(1)先引入两个概念,同余类和剩余系。
对于
例如:模
此外,简化剩余系关于模
因为
(2) 接下来证明欧拉定理。
设
证明(A):
由于
证毕 (A)
综上:
因此
证明:
设
则
tip :注意第一条定理,和
证明:
先引入一个结论:若
这个移项后发现
受这个启发,我们先把
,再把它们乘起来,原式得证。
接下来,我们分析如何证上面的式子。
若
若
由于
所以
因此
tip 1:关于
tip 2:不等式里"忽视"
tip 3:如果不理解因数,注意
扩展欧几里得
- 裴蜀定理:对于任意整数
,存在一对整数 ,满足 。
证明:
- 根据
,在最后 时,显然有 满足 - 设当前
- 故
。如此递归便能构造。
- 此过程还算了上述方程的一组特解,该算法为扩展欧几里得算法。代码实现:
void exgcd(int a,int b,int &x,int &y,int &d)
{
if(b==0) return x=1,y=0,d=a,void();
exgcd(b,a%b,x,y,d);
int t=x;x=y,y=t-a/b*y;
}
- 求
的整数解。
- 先判是否有解。设
,有解当且仅当 。 - 特解:用 exgcd 求出方程
的特解 ,再同时乘上 ,就得到原方程的特解 。 - 通解:
。 过程:假设将
扩大为 , 观察法发现需要减小,设减小为 。( ,事实上是我们希望 为正整数) 解
得:
,又 ,所以 。 令
最小(也就是最小偏差量),就可以表示通解了。不难得出 , 同理,得 。
- 求解线性同余方程
。
移项得
,即 使 的倍数。假设为 倍,则 。就是刚刚的不定方程。
同余数最短路
一般用来解决用若干整数使用任意次拼数这类的问题。
显然可以用完全背包来解决,复杂度
若用 dijkstra 求解最短路,设这若干整数绝对值最小的为
假设我们用
选定
由此,考虑
问题转化为如何求每个同余类最小能被凑出的数。
这可以转化为一个图论问题。对每个同余类建一个节点,向其加
代码实现:
for(int i=0;i<a[1];i++)
for(int j=2;j<=n;j++)
G[i].emplace_back((i+a[j])%a[1],a[j]);
dijkstra();// spfa or other algorithms
// 假设要求统计能凑出 [l,r] 中多少不同的数。
ll ans=0;
for(int i=0;i<a[1];i++)
{
if(r>=d[i]) ans+=(r-d[i])/a[1]+1;
if(l-1>=d[i]) ans-=(l-1-d[i])/a[1]+1;
}
逆元
实用主义定义:考虑一个分数
求逆元:
- 设逆元为
,根据定义, 。 即 。 - 若
为质数,根据费马小定理 ,得 。 - 若
互质,则用 exgcd 求解上述线性同余方程(所以逆元可能不存在)。
- 线性求逆元:
- 设当前要求
的逆元, ,即 。 - 同时乘
: 。 。- 为了方便,让逆元非负,右边加上
(此数为 的倍数,模 意义下显然允许),得最终式子: 。
inv[1]=1;
for(int i=2;i<=n;i++) inv[i]=1ll*(p-p/i)*inv[p%i]%p;
- 线性求阶乘逆元:
inv[n]=qpow(fac[n],p-2);
for(int i=n-1;~i;i--) inv[i]=1ll*inv[i+1]*(i+1)%p;
中国剩余定理及扩展
设
有整数解
证明:
若要求最小正整数解,只需每步对
代码实现:
void CRT()
{
ll p=1,ans=0;
for(int i=1;i<=n;i++) p*=p[i];
for(int i=1;i<=n;i++)
{
int P=p/p[i],t,y;
exgcd(P,p[i],t,y);
ans+=1ll*a[i]*P*t;
}
}
题外话
机房大佬叶师傅跟我提到过可以用 CRT 还原被取模数。
但我懒,没试过。
扩展中国剩余定理
考虑
假设已经求出前
现在要满足
那么前
取模技巧:
- 在解上述非线性同余方程时,让
对 取模,并变为正数。 - 求出新的
解后,对新的 取模。
代码实现:
void exgcd(int a,int b,int &x,int &y,int &d)
{
if(!b) return x=1,y=0,d=a,void();
exgcd(b,a%b,x,y,d);
int t=x;x=y,y=x-a/b*y;
}
void EXCRT()
{
int m=1;
for(int i=1;i<=n;i++)
{
cin>>a>>p;
int k,y,d;//d=gcd(m,p)
a=((a-x)%p+p)%p;//取个模
exgcd(m,p,k,y,d);
if(a%d!=0) return puts("No solution"),void();//判断无解
k=k*a/d;
x+=k*m;
m=lcm(m,p);
x=(x%m+m)%m;
}
}
高次同余方程
BSGS 算法及扩展
是用来解决形如
若保证
那么得到一种朴素算法:从
BSGS 是对此算法的改进。其实不难想到分块。
设
取
把
为方便,直接取
代码实现:
int BSGS()
{
unordered_map<int,int> mp; //相当于哈希表
int t=sqrt(p)+1,A=1;
for(int i=1;i<=t;i++)
A=A*a%p,mp[b*A%p]=i;
int now=A;//now=a^t
for(int i=1;i<=t;i++)
{
auto it=mp.find(now);
if(it!=mp.end()) return i*t-it->second;
now=now*A%p;
}
return -1;
}
若
根据裴蜀定理,当
注意当
细节很多,上代码:
//k 是系数,注意 BSGS 时也要传
int exBSGS(int a,int b,int p,int k=1)
{
a%=p,b%=p;
if(b==1||p==1) return 0;
int d;
for(int i=0;;i++)
{
d=gcd(a,p);
if(b%d) return -1;
b/=d,p/=d;
k=k*a/d%p;
if(k==b) return i+1;
if(d==1) return BSGS(a,b,p,k)+i+1;
}
}
原根
咕咕咕。
高斯消元
说人话就是解线性方程组。
可参考解二元一次方程组的过程。
直接说做法,可自行模拟。
每个未知数选择一个方程组,用选择的方程组消去其余方程组的当前未知数。
最后每个未知数等于选择的方程组的最终常数。
给一个过程方便理解。
如以下方程组:
先消去第一个未知数:
再消去第二个未知数:
再消去第三个未知数:
每个未知数的值显而易见了。
如果出现
这样的方程: 若
,则有多个解,当前未知数称为自由元。 若
,则方程组无解。
那么为了方便,每次找到对于当前未知数含有最大系数的方程组,如果系数
其实这么说还是很抽象。直接看代码。
void gauss(vector<vector<double>> &a)
{
//读入系数,a[i][n+1] 是等号后面的常数
for(int i=1;i<=n;i++)
for(int j=1;j<=n+1;j++)
a[i][j]=rd();
for(int i=1;i<=n;i++)
{
//现在考虑第 i 个未知数
//寻找对于第 i 个未知数具有最大系数的方程组
int mx=i;
//注意前 i-1 个方程组已使用
for(int j=i+1;j<=n;j++)
if(fabs(a[j][i]-a[mx][i])>eps) mx=i;
if(fabs(a[mx][i])<eps) {puts("No solution");return;}//判断无解
swap(a[i],a[mx]);
for(int j=1;j<=n;j++)//枚举每个方程组
if(i!=j)
for(int k=i+1;k<=n+1;k++)
a[j][k]-=a[j][i]/a[i][i]*a[i][k];
}
for(int i=1;i<=n;i++) a[i][n+1]/=a[i][i];
}
还有一种遇的比较多的,异或方程组。就是加号变成了异或运算。
那么此时的系数就为
为方便,可以把每个方程组进行状态压缩。及用一个整数表示。
如果位数过多,存不下,考虑用 bitset 优化常数。
void gauss()
{
for(int i=1;i<=n;i++)
{
//变化一下思路,找最大的 a[i],也就是主元位数最高的 a[i]
for(int j=i+1;j<=n;j++)
if(a[j]>a[i]) swap(a[i],a[j]);
if(a[i]==1) {puts("No solution");return;}//出现 0=1 的方程,无解
for(int k=n;k;k--)
if(a[i]>>k&1)//以 a[i] 最高位为主元,消去其他方程该位的系数
{
for(int j=1;j<=n;j++)
if(i!=j&&(a[j]>>k&1)) a[j]^=a[i];
break;
}
}
}
本文作者:spider_oyster
本文链接:https://www.cnblogs.com/spider-oyster/p/17494945.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!