数论
前言:OI-WiKi 和 知乎 里都讲得挺好, 如果哪里没理解一定要弄懂,这里只是总结归纳。
1. 唯一分解定理等
约束个数:
约束和:
2. 欧拉函数
式子
性质
.- 若p是一个素数,则
. - 若p是一个素数,则
. - 对于任意两个正整数
, 且 ,则 .特别的,对于奇数n .
变形
- p为素数,若
, 则 . - p为素数,若
, 则 . - 与
互质的数都是成对出现的,且每对的和为 ,所以大于2的数的 都为偶数.
代码
int phi_(int n) { int ans=n; for(int i=2;i*i<=n;i++) { if(n%i==0) { ans=ans-ans/i; while(n%i==0) { n/=i; } } } if(n>1) ans=ans-ans/n; return ans; } int phi[maxn], cnt, p[maxn], vis[maxn]; void get_phi(int n)//求1到n的所有欧拉函数值 { phi[1]=1; for(int i=2;i<=n;i++) { if(!vis[i]) { p[cnt++]=i;//质数 phi[i]=i-1;//质数i的欧拉函数为i-1 } for(int j=0;i*p[j]<=n;j++) { int m=i*p[j]; vis[m]=1; if(i%p[j]==0) { phi[m]=p[j]*phi[i]; break; } else phi[m]=(p[j]-1)*phi[i]; } } }
3. 欧拉定理
若
4. 费马小定理(要求模数为素数)
若
特殊的,若
5. 扩展欧拉定理
代码就不展示了
6. 扩展欧几里得
用于求
link:https://oi-wiki.org/math/number-theory/linear-equation/
这个真的非常重要。
构造通解
(考虑
示例
求不定方程
- 若
能被 整除,则有整数解。
先用扩展欧几里德求 的解,再乘以 ,即得原方程特解 。 - 若
不能被 整除 ,则无整数解。
代码
void exgcd(int a,int b,int &x,int &y) { if(b==0) { x=1; y=0; return ; } exgcd(b, a%b, x, y); int t=x; x=y; y=t-a/b*y; }
int exgcd(int a,int b,int &x,int &y) { if(b==0) { x=1; y=0; return a; } int ans=exgcd(b, a%b, x, y); int k=x; x=y; y=k-a/b*y; return ans; }
函数返回值为gcd,
7. 乘法逆元
若
注意:并非所有的情况下都存在乘法逆元,当
用法
求
代码
下列p均为模数。
费马小定理求逆元(p为素数)
qpow(a, p-2, p);
线性求逆元
ny[1]=1; for(int i=2;i<p;i++) { ny[i]=(long long)(p-p/i)*ny[p%i]%p; }
扩展欧几里得求逆元
int exgcd(int a,int b,int &x,int &y) { if(b==0) { x=1; y=0; return a; } int ans=exgcd(b, a%b, x, y); int k=x; x=y; y=k-a/b*y; return ans; } if(exgcd(b, p, x, y)==1) cout<<(x%p+p)%p; else cout<<-1;
8. 组合数
也都挺好理解的。
代码
int qpow(int a,int b) { int res=1; while(b) { if(b&1) { res=res*a%mod; } a=a*a%mod; b>>=1; } return res; } void init() { f[0]=g[0]=1; for(int i=1;i<maxn;i++) { f[i]=(f[i-1]*i)%mod; g[i]=(g[i-1]*qpow(i, mod-2))%mod; } } int getC(int n, int m) { if(n<m||m<0) { return 0; } return f[n]*g[m]%mod*g[n-m]%mod; }
假如时间复杂度很紧,建议将预处理函数换成以下写法:
inline void init(int n) { f[0]=g[0]=1; for(int i=1;i<=n;i++) { f[i]=f[i-1]*i%mod; } g[n]=qpow(f[n], mod-2); for(int i=n-1;i>=0;i--) { g[i]=g[i+1]*(i+1)%mod; } }
掉过的坑:
- 如果无输出,很可能是N的值太大了。
- 如果输出为0,很可能没有使用init()。
- 如果模数很小,要考虑lucas定理。
- 当没有模数的时候,直接用组合的公式即可(
)。 - 如果不明情况TLE或RE,可以考虑换写法。
补充
可以用杨辉三角推出这样一个式子:
就像这样子:
void init() { for(int i=0;i<=n;i++) c[i][0]=1; for(int i=0;i<=n;i++) { for(int j=1;j<=i;j++) { c[i][j]=c[i-1][j-1]+c[i-1][j]; } } }
9. 错排问题
虽然课件里没有(好像有),但还是挺有意义的,记一下吧。
概要:有
然后通项公式为
证明见这篇博客。
代码:直接预处理出d数组即可。
10. lucas 定理
当p是一个不大的质数时,
证明见老师ppt。
代码
int lucas(int n,int m, int p) { if(m==0) return 1; return lucas(n/p, m/p, p)*getC(n%p, m%p, p)%p; }
11. 二项式反演
记
若已知
若已知
上述已知
12. 波动数列
貌似不是数论??
波动数列:字面意思,每个元素大小波动起伏的数列。(其实我也不知道这是个什么数列,如果有明确定义麻烦告诉我)
性质
- 在一个波动数列中,若两个
与 相邻,那么我们直接交换这两个数字就可以组成一个新的波动数列。 - 把波动数列中的每个数字
变成 会得到另一个波动数列,且新数列的山峰与山谷情况相反。 - 波动序列有对称性。
13. CRT/中国剩余定理
小学奥数---------韩信点兵
规范一点:
crt就是求解如下形式一元线性同余方程组
求x的最小整数解。其中模数两两互质。
过程+证明见老师ppt。
代码
void exgcd(int a,int b,int &x,int &y) { if(b==0) { x=1; y=0; return ; } exgcd(b, a%b, x, y); int t=x; x=y; y=t-a/b*y; } int crt(int m[], int r[]) { int M=1, ans=0; for(int i=1;i<=n;i++) M=lcm(M, m[i]); for(int i=1;i<=n;i++) { int c=M/m[i], x, y; exgcd(c, m[i], x, y); ans=(ans+r[i]*c*x%M)%M; } return (ans%M+M)%M; }
那么对于模数不互质时,
14. EXCRT/扩展中国剩余定理
就是在crt的基础上不保证模数两两互质。
int exgcd(int a,int b,int &x,int &y) { if(b==0) { x=1; y=0; return a; } int ans=exgcd(b, a%b, x, y); int t=x; x=y; y=t-a/b*y; return ans; } int excrt(int m[], int r[]) { int m1, m2, r1, r2, p, q; m1=m[1], r1=r[1]; for(int i=2;i<=n;i++) { m2=m[i], r2=r[i]; int d=exgcd(m1, m2, p, q); if((r2-r1)%d) return -1;//无解 p=p*(r2-r1)/d; p=(p%(m2/d)+m2/d)%(m2/d); r1=m1*p+r1; m1=m1*m2/d; } return (r1%m1+m1)%m1; }
关于CRT的用处:将不是质数的模数分解,最后crt合并。注意数组大小以及循环变量的大小。
15. 卡特兰数
upd on. 24.7.27
为了不让它成为遗忘的过去,我一定要再学一遍!
卡特兰数时一个用于计数问题的序列,就像斐波那契数列一样,它的前几项是:
通项公式
递推式
特征
一种操作数不能超过另一种操作数、两种操作不能有交集、给定一个移动范围,通常是卡特兰数。
16. Prufer序列
具体操作
(1) 每次选择编号最小的叶节点,将它和它与父节点的连边删去,将它的父节点存入序列。
(2) 循环操作(1),直至无根树里仅剩两个节点。
代码
将无根树转换为
void solve1() { for(int i=1;i<n;i++) { cin>>f[i]; deg[f[i]]++; } int cur=1;//最小叶节点 for(int i=0;i<=n-2;) { while(deg[cur]) { cur++; } p[++i]=f[cur]; while(!--deg[p[i]]&&p[i]<cur) { p[i+1]=f[p[i]]; i++; } cur++; } ans=0; for(int i=1;i<=n-2;i++) { ans^=(1ll*i*p[i]); } cout<<ans<<endl; }
将无根树转换为
void solve2() { for(int i=1;i<=n-2;i++) { cin>>p[i]; deg[p[i]]++; } int cur=1; for(int i=0;i<=n-2;) { while(deg[cur]) { cur++; } f[cur]=p[++i]; while(!--deg[p[i]]&&p[i]<cur) { f[p[i]]=p[i+1]; i++; } cur++; } f[p[n-2]]=n; ans=0; for(int i=1;i<=n-1;i++) { ans^=(1ll*i*f[i]); } cout<<ans<<endl; }
时间复杂度为
性质
序列与无根树一一对应。- 度数为
的节点在 序列中出现 次。 - 一个
个节点的完全图的生成树个数为 。 - 对于给定度数为
的无根树有 种情况。
17. Bsgs算法
给定整数
求满足
具体操作
由扩展欧拉定理
令
则
- 先枚举j,把(
)丢入哈希表,结果相同,则取较大的j。 - 再枚举i,计算
,到哈希表中查找是否有相等值,找到第一个结束。最小的 。
代码
int bsgs(int a,int b,int p) { a%=p; b%=p; if(b==1) return 0; int m=ceil(sqrt(p)),t=b; h[b]=0; for(int i=1;i<m;i++) { t=t*a%p; h[t]=i; } int mi=1; for(int i=1;i<=m;i++) { mi=mi*a%p; } t=1; for(int i=1;i<=m;i++) { t=t*mi%p; if(h.count(t)) return i*m-h[t]; } return -1; }
时间复杂度为
18. exBsgs算法
假如
这个思路挺好想的,代码如下:
代码
int inv(int a,int p) { int x=0, y=0; exgcd(a, p, x, y); return (x%p+p)%p; } int exbsgs(int a,int b,int p) { a%=p, b%=p; if(b==1||p==1) return 0; if(!a) { if(b) return -1; return 1; } int k=0, t=1; while(__gcd(a, p)!=1) { int gcd=__gcd(a, p); if(b%gcd) return -1; ++k, b/=gcd, p/=gcd, t=t*(a/gcd)%p; if(t==b) return k; } int ans=bsgs(a, b*inv(t, p)%p, p); if(ans==-1) return -1; return ans+k; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效