window.cnblogsConfig = { homeTopImg: [ "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png", "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png" ], }

数论基础

欧拉函数

定义

欧拉函数 ϕ(n) 代表的是 [1,n] 之间与 n 互质的数量。

公式

ϕ(n)=n×(11p1)×(11p2)×(11p3)××(11pk)

其中:nk 个质因数,而 pi 就是其中的一个质因数。

推导

如何推导

推导的方式要用到容斥原理

欧拉函数 ϕ(n) 代表的是 [1,n] 之间与 n 互质的数量,但是我们发现比较难想,于是正难则反,直接从不互质来入手,然后减掉就得出结果了。

如果 i 是不互质,那么 in 必将有共同的质因数。所以,我们可以通过 n 的每一个质因数进行倍数处理,然后容斥算出有多少是不互质的,因此也就可得出答案了。

推导过程

我们设:n 的质因数是:{ p1,p2,,pk }。

那么 pi[1,n] 这个区间中的倍数就有 npi

为了方便推导,我们暂且先设 k=3

于是就可以得到公式:

  • nnp1np2np3+np1×p2+np1×p3+np2×p3np1×p2×p3

在进行通分之后,我们发现这个式子其实已经等于 ϕ(n) 了。

得证。

代码

定义法

  • 只能处理一个数的 ϕ

  • 思路是一边分解质因数一遍处理欧拉函数

    int get_euler(int n) { int euler = n; for (int i = 2; i <= n / i; i++) { if (n % i == 0) { euler = euler / i * (i - 1) //euler = euler * (1 - 1 / i); while (n % i == 0) n /= i; } } if (n > 1) euler = euler / n * (n - 1); return euler; }

线性筛法

  • 在进行筛法的时候处理欧拉函数

  • 可以在一次线性筛中处理出 [1,n] 中的欧拉函数

    int primes[N], cnt = 0; int euler[N]; bool st[N]; int get_euler(int n) { euler[1] = 1; // 1的欧拉函数值是1 // 线性筛模板 + 过程中求欧拉函数 for (int i = 2; i <= n; i++) { if (!st[i]) { primes[cnt ++] = i; euler[i] = i - 1; // (1)式 } for (int j = 0; primes[j] <= n / i; j++) { st[i *primes[j]] = true; if (i % primes[j] == 0) { euler[i * primes[j]] = euler[i] * primes[j]; //(2)式 break; } euler[i * primes[j]] = euler[i] * (primes[j] - 1);//(3)式 } } // 最后,euler数组中存的就是 1 ~ n 每个数的欧拉函数值 } urn euler; }

一些等式

  • n 是质数的时候,ϕ(n)=n1

  • n 是质数的时候,存在 nk 使得 ϕ(nk)=(n1)×nk1

  • 积性函数,如果 nm 互质,则 ϕ(n×m)=ϕ(n)×ϕ(m)

参考文献:

费马小定理

如果 p 是一个质数,a 不是 p 的倍数。那么必然存在: ap11(modp)

谁想推啊!所以不推了。(~逃

逆元

逆元的定义

如果一个线性同余方程,p 为质数,ap 互质,那么 ax1(modp)x 就是 ap 的逆元。

快速幂求逆元

单个求,时间复杂度 logn 。适合单个求。

推导

根据费马小定理我们可以知道:ap11(modp)

a×ap21(modp)

所以什么?很容易看出来 x=ap2

注意了,我们是由费马小定理推出 x 的值的,但是我们不能根据这个式子去推导费马小定理。

代码

记得 #define int long long~

inline ll ksm(int a,int b){ ll ans = 1; while(b){ if(b & 1) ans = mod(ans * a,p); b >>= 1,a = mod(a * a,p); }return ans; } inline void solve(){//快速幂求逆元 cin >> n >> p; for(re int i(1);i <= n;++i){ println(mod(ksm(i,p-2),p)); } }

线性求逆元

整体的思想就是利用已知的数据从前往后推,以达到 O(n) 复杂度的需求,适合求多组数据的题目。

首先,我们有一个 111(modp)

设:p=k×i+r,其中 1<r<i<p 。换句话说,kp/i 的商,r 是余数。

将这个式子放在 (modp) 这个意义下,得到 k×i+r0(modp)

k×i+k0(modp)

乘上 i1r1 k×r1+i10(modp)

移项 i1k×r1(modp)

将,kp/i 的商,r 是余数带入 i1pi×(pmodi)1(modp)

这样我们就可以利用递推来完成计算逆元的操作了。

注意:因为是存在负数,所以我们取模操作的时候就要 (x + mod) % mod

void solve(){ //线性求逆元 ans[1]=1; cin >> n >> p; cout<<1<<endl; for(int i(2);i <= n;++i){ ans[i] = (-p / i * ans[p % i] + p) % p; cout<<(ans[i] + p) % p<<endl; } }

参考文献:

扩展欧几里得exgcd

扩展欧几里得算法是用来求解 ax+by=gcd(a,b) 的一种可行解。

推导过程

设:

ax1+by1=gcd(a,b)

bx2+(amodb)y2=gcd(b,amodb)

gcd(a,b)=gcd(b,amodb)

ax1+by1=bx2+(amodb)y2

amodb=a(ab×b)

ax1+by1=bx2+[a(ab×b)]y2

ax1+by1=bx2+ay2(ab×b)y2

ax1+by1=ay2+b×(x2ab×y2)

a=a,b=b

x1=y2,y1=x2ab×y2

一直递归迭代即可。

代码

int Exgcd(int a, int b, int &x, int &y) { if (!b) { x = 1; y = 0; return a; } int d = Exgcd(b, a % b, x, y); int t = x; x = y; y = t - (a / b) * y; return d; }

线性同余方程

定义

axb(modn)

找出这个方程的可行解。( x 为未知数 )。

Exgcd 解法

首先:可以将式子变为 ax+kn=b

其中 xk 是未知数。对于整数解的充要条件是 gcd(a,n)|b 也就是 bmodgcd(a,n)=0

我们先根据 Exgcd 来找出一组 x0k0ax0+nk0=gcd(a,n)

然后等式同除 gcd(a,n),再乘 b

得到:

abgcd(a,n)x0+nbgcd(a,n)k0=b

那么这就是这个方程的可行解。

当: gcd(a,n)=1x0k0ax+kn=b 的一组解,那么:

x=x0+nt

k=k0nt

为啥,我就不知道了。这对于任意整数 t 都是正确的

一般来说,题目让你求最小解,所以只需要求 (xmodt+t)modt

其中就有:t=ngcd(a,n)

code

int ex_gcd(int a, int b, int& x, int& y) { if (b == 0) { x = 1; y = 0; return a; } int d = ex_gcd(b, a % b, x, y); int temp = x; x = y; y = temp - a / b * y; return d; } bool liEu(int a, int b, int c, int& x, int& y) { int d = ex_gcd(a, b, x, y); if (c % d != 0) return 0; int k = c / d; x *= k; y *= k; return 1; }

中国剩余定理(CRT)

对于 k 个同余式,求最小非负的 x

i 个同余式。

xai(modmi)

对于这个方程组需要解 x

题目保证所有 m 两两互质。

M 为所有模数的积,模数都是 m+iMi=M/miti×Mi1(modmi) 也就是一个逆元,所以直接Exgcd 搞定 ti。。

xi=1naiMiti(modM)

这样就可以求得x的最小解了。

我们要证明 x 在任意 i(1ik) 条式子中是成立的

i 不等于 j 的时候,有 Mj0(modmi)

所以 cjMj0(modmi)

又有 ciMi×(Mi1modmi)1(modmi)

所以我们有:

xi=1naiMiti(modmi)

aiMi×(mi1modmi)(modmi)

ai(modmi)

奥管他推的对不对,知道公式不就是了!!

#include <bits/stdc++.h> using namespace std; #define ll long long const ll N = 1e5+5; ll n; ll pr[N],A[N]; ll exgcd(ll a,ll b,ll &x,ll &y){ if(!b){ x=1,y=0; return a; } ll g = exgcd(b,a%b,y,x); y -= a/b*x; return g; } ll crt(){ ll ans=0; ll M=1; for(int i =1;i<=n;i++) M*=pr[i]; for(int i = 1;i <= n;i++){ ll m=M/pr[i]; ll x,y; exgcd(m,pr[i],x,y); ll t= m*x%M*A[i]%M; ans = (ans + t) % M; } return (ans+M) %M; } int main(){ cin >> n; for(int i = 1;i <= n;i++){ scanf("%lld%lld",&pr[i],&A[i]); } printf("%lld\n",crt()); return 0; }

拓展中国剩余定理(EXCRT)

认为还是金钩爷讲的好,证明其实感觉。。。哎算了,就把证明发这了。反正正常用直接套公式。

现在考虑合并两个同余方程的时候该怎么做。

{xb(moda) xB(modA)

这个可以写作:

{x=ay+b x=AY+B

由于x是一个常量,因此ay+b=AY+B,其中a,b,A,B都是常数

整理一下后就是ay+A(Y)=Bb

通过exgcd,可以获得一组关于ay+A(Y)=gcd(a,A)的解(y0,Y0)

a(y0×Bbgcd(a,A))+A(Y0×Bbgcd(a,A))=gcd(a,A)×Bbgcd(a,A)=Bb

由于x=ay+b,现在要最小化x,也就是最小化y,即最小化t=y0×Bbgcd(a,A)

由于t的通解为t+Agcd(a,A)k,因此t的最小值为t0=tmodAgcd(a,A)

于是就可以将这两个同余方程合并为

xa×t0+b(modlcm(a,A))

参考文献:https://www.luogu.com.cn/blog/KingSann/solution-p4777

对于洛谷 P4777 的模板代码如下:

signed n; int A, B, a, b, d, x, y; void exgcd(int &x, int &y, int a, int b) { if (!b) { d = a, x = 1, y = 0; } else exgcd(y, x, b, a % b), y -= a / b * x; } void merge() { exgcd(x, y, a, A); int c = B - b; if (c % d) { cout << -1; exit(0); } else { x = ((x * c / d) % (A / d) + (A / d)) % (A / d); int m = a * A / __gcd(a, A); b = ((a * x + b) % m + m) % m ; a = m; } } void excrt() { cin >> n; for (int i = 1; i <= n; i++) { ll x, y; cin >> x >> y; A = x, B = y; if (i != 1) merge(); else a = x, b = y; } cout << (ll)(b % a); }

__EOF__

本文作者gsczl71
本文链接https://www.cnblogs.com/gsczl71/p/17870948.html
关于博主:GDSZ初一蒟蒻,明年拿下七级勾
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   gsczl71  阅读(122)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
点击右上角即可分享
微信分享提示