数论笔记-同余
同余
带余数除法
带余数除法的定义与基本性质
定义 对于整数 a,ba,b 且 b≠0b≠0 ,一定存在整数 q,rq,r 满足 a=qb+ra=qb+r ,其中 r∈[0,b)r∈[0,b) ,称 qq 为 aa 除以 bb 的商, rr 为 aa 除以 bb 的余数。
注意,c++的 %
模运算指定 rr 和 aa 同号, /
整除运算指定正数取下整、负数取上整,都需要辨别。
模运算的定义 amodbamodb 的结果为 aa 除以 bb 的余数 rr ,运算优先级同乘除。
模 mm 加法 (a+b)modm(a+b)modm 。
模 mm 减法 (a−b)modm(a−b)modm 。
模 mm 乘法 abmodmabmodm 。
性质1 模运算与整除的关系
性质2 模运算的运算规律
性质3 amodn=amodm=xamodn=amodm=x 且 gcd(n,m)=1gcd(n,m)=1 ,则 amodnm=xamodnm=x
性质3的证明:
由同余式可得 a−x=k1n=k2ma−x=k1n=k2m ,又 gcd(n,m)=1gcd(n,m)=1 ,根据整除基本性质4,所以 m∣k1n⟺m∣k1m∣k1n⟺m∣k1 ,所以设 k1=k3mk1=k3m ,于是 a−x=k3mna−x=k3mn ,即 amodnm=xamodnm=x 。
模运算加速算法
模运算封装
强大模板,告别打 %
打到手酸。
常数较大。
时间复杂度
- 求幂 O(logk)O(logk)
- 逆元 O(logP)O(logP)
- 其余 O(1)O(1)
空间复杂度 O(1)O(1)
template<int P> struct ModInt { int val; ModInt(ll _val = 0) { if (2 * P <= _val) val = _val % P; else if (P <= _val) val = _val - P; else if (0 <= _val) val = _val; else if (-P <= _val) val = _val + P; else val = (_val % P + P) % P; } // 快速幂 ModInt qpow(ll k) const { ModInt a = *this, ans = 1; while (k) { if (k & 1) ans = ans * a; k >>= 1; a = a * a; } return ans; } // 矩阵初等变换求逆 ModInt inv() const { int a = val, b = P, u = 1, v = 0; while (b) { int t = a / b; swap(a -= t * b, b); swap(u -= t * v, v); } return { u }; } ModInt &operator+=(const ModInt &x) { val + x.val >= P ? val += x.val - P : val += x.val; return *this; } ModInt &operator-=(const ModInt &x) { val - x.val < 0 ? val -= x.val - P : val -= x.val; return *this; } ModInt &operator*=(const ModInt &x) { val = 1LL * val * x.val % P; return *this; } ModInt &operator/=(const ModInt &x) { return val % x.val == 0 ? val /= x.val, *this : *this *= x.inv(); } friend ModInt operator+(ModInt a, const ModInt &b) { return a += b; } friend ModInt operator-(ModInt a, const ModInt &b) { return a -= b; } friend ModInt operator*(ModInt a, const ModInt &b) { return a *= b; } friend ModInt operator/(ModInt a, const ModInt &b) { return a /= b; } friend ModInt operator-(const ModInt &x) { return { -x.val }; } friend bool operator==(const ModInt &a, const ModInt &b) { return a.val == b.val; } friend bool operator!=(const ModInt &a, const ModInt &b) { return a.val != b.val; } friend istream &operator>>(istream &is, ModInt &x) { ll _x; is >> _x; x = { _x }; return is; } friend ostream &operator<<(ostream &os, const ModInt &x) { return os << x.val; } }; using MInt = ModInt<P>;
龟速乘
用于可能爆 long long
数字的乘法取余。
不过一般可以用 __int128
替代了。
时间复杂度 O(logb)O(logb)
空间复杂度 O(1)O(1)
const int P = 1e9 + 7; ll qmul(ll a, ll b) { ll ans = 0; while (b) { if (b & 1) ans = (ans + a) % P; b >>= 1; a = (a << 1) % P; } return ans; }
快速幂
常用的整数幂次取余,aa 越大速度越慢。
时间复杂度 O(logk)O(logk)
空间复杂度 O(1)O(1)
const int P = 1e9 + 7; int qpow(int a, ll k) { int ans = 1; while (k) { if (k & 1) ans = 1LL * ans * a % P; k >>= 1; a = 1LL * a * a % P; } return ans; }
同余的定义与基本性质
定义 若整数 a,ba,b 模 mm 的余数相等,则称 a,ba,b 模 mm 同余,记作 a≡b(modm)a≡b(modm) 。若余数不等,则称 a,ba,b 模 mm 不同余,记作 a≢b(modm)a≢b(modm) 。
约定 由于对负模数 mm 的讨论等价于正模数的讨论,所以若不特殊指明,我们假定模数 m>0m>0 。
性质1(自反性) a≡a(modm)a≡a(modm) 。
性质2(对称性) a≡b(modm)⟺b≡a(modm)a≡b(modm)⟺b≡a(modm) 。
性质3(传递性) a≡b(modm) 且 b≡c(modm)⇒a≡c(modm)a≡b(modm) 且 b≡c(modm)⇒a≡c(modm) 。
性质4(同加性)
性质5(同乘性)
性质6(同幂性) a≡b(modm)⇒ak≡bk(modm)a≡b(modm)⇒ak≡bk(modm) ,其中 k≥0k≥0。
性质7(同除性) a≡b(modm)⟺ad≡bd(modmgcd(m,d))a≡b(modm)⟺ad≡bd(modmgcd(m,d)) ,其中 dd 满足 d∣a,d∣bd∣a,d∣b 。
性质8 若 a≡b(modm)a≡b(modm) ,那么 m′∣m⇒a≡b(modm′)m′∣m⇒a≡b(modm′) 。
性质9 a≡b(modmi)(i=1,2,⋯,k)⟺a≡b(modM)a≡b(modmi)(i=1,2,⋯,k)⟺a≡b(modM) ,其中 M=lcm(m1,m2,⋯,mk)M=lcm(m1,m2,⋯,mk) 。
性质10 a≡b(modm)⇒gcd(a,m)=gcd(b,m)a≡b(modm)⇒gcd(a,m)=gcd(b,m) 。
性质9的证明:
由 a≡b(modmi)(i=1,2,⋯,k)a≡b(modmi)(i=1,2,⋯,k) 可得 mi∣a−bmi∣a−b ,根据整除基本性质11,可得 M∣a−b,M=lcm(m1,m2,⋯,mk)M∣a−b,M=lcm(m1,m2,⋯,mk) ,因此 a≡b(modM)a≡b(modM) 。
同余类与剩余系的定义与基本性质
同余类的定义 对于 a∈[0,m−1]a∈[0,m−1] ,集合 {a+km},k∈Z{a+km},k∈Z 的所有数模 mm 同余 aa ,称这个集合为模 mm 的一个同余类 ¯a¯¯¯a 。
完全剩余系的定义 模 mm 的同余类有 mm 个,分别为 ¯0,¯1,⋯,¯m−1¯¯¯0,¯¯¯1,⋯,¯¯¯¯¯¯¯¯¯¯¯¯¯¯m−1 ,它们构成了 mm 的完全剩余系。
既约剩余系的定义 模 mm 的同余类有 φ(m)φ(m) 个的代表元与 mm 互质,它们构成了 mm 的既约剩余系(简化剩余系)。
φ(m)φ(m) 为欧拉函数,下一节会讲到。
性质1 设 SS 是模 mm 的一个完全剩余系,若 gcd(k,m)=1gcd(k,m)=1 ,则 S′={x′∣x′=kx+b,x∈S}S′={x′∣x′=kx+b,x∈S} 也是模 mm 的一个完全剩余系。
性质2 设模 m1m1 的完全剩余系为 S1S1 ,模 m2m2 的完全剩余系为 S2S2 ,且 gcd(m1,m2)=1gcd(m1,m2)=1 ,则模 m=m1m2m=m1m2 的完全剩余系为 S={x∣x=m2x1+m1x2,x1∈S1,x2∈S2}S={x∣x=m2x1+m1x2,x1∈S1,x2∈S2} 。
性质3 设 SS 是模 mm 的一个既约剩余系,若 gcd(k,m)=1gcd(k,m)=1 ,则 S′={x′∣x′=kx,x∈S}S′={x′∣x′=kx,x∈S} 也是模 mm 的一个既约剩余系。
性质4 设模 m1m1 的既约剩余系为 S1S1 ,模 m2m2 的既约剩余系为 S2S2 ,且 gcd(m1,m2)=1gcd(m1,m2)=1 ,则模 m=m1m2m=m1m2 的既约剩余系为 S={x∣x=m2x1+m1x2,x1∈S1,x2∈S2}S={x∣x=m2x1+m1x2,x1∈S1,x2∈S2} 。
- 推论1(性质4的推论) gcd(m1,m2)=1⇒φ(m1m2)=φ(m1)φ(m2)gcd(m1,m2)=1⇒φ(m1m2)=φ(m1)φ(m2) ,即欧拉函数是积性函数。
性质1的证明:
假设存在 kxi+b≡kxj+b(modm)kxi+b≡kxj+b(modm) ,则 kxi≡kxj(modm)kxi≡kxj(modm) 。因为 gcd(k,m)=1gcd(k,m)=1 ,所以 xi≡xj(modm)xi≡xj(modm) ,xi,xjxi,xj 属于一个同余类,矛盾。综上,得证。
性质2的证明:
假设存在 m2x1+m1x2≡m2x′1+m1x′2(modm)m2x1+m1x2≡m2x′1+m1x′2(modm) ,那么 m2(x1−x′1)≡m1(x′2−x2)(modm)m2(x1−x′1)≡m1(x′2−x2)(modm) 。因为 m1∣mm1∣m ,所以 m2(x1−x′1)≡m1(x′2−x2)≡0(modm1)m2(x1−x′1)≡m1(x′2−x2)≡0(modm1) ,又 gcd(m1,m2)=1gcd(m1,m2)=1 ,所以 x1−x′1≡0(modm1)x1−x′1≡0(modm1) ,矛盾。综上,得证。
性质3的证明:
根据性质1证明,类似可得 S′S′ 一定是 mm 的一个剩余系,且有 φ(m)φ(m) 个元素,接下来只需证明任意元素都与 mm 互质。
任意 x∈Sx∈S ,有 gcd(x,m)=1gcd(x,m)=1 ,又 gcd(k,m)=1gcd(k,m)=1 ,所以 gcd(kx,m)=1gcd(kx,m)=1 ,所以 S′S′ 是模 mm 的既约剩余系。
性质4的证明:
根据性质2证明,类似可得 SS 一定是模 mm 的一个剩余系,且有 φ(m1)⋅φ(m2)φ(m1)⋅φ(m2) 个元素,但我们并不知道 φ(m1)⋅φ(m2)=φ(m1m2)φ(m1)⋅φ(m2)=φ(m1m2) ,因此我们证明 SS 的元素都与 mm 互质只能说明 SS 是模 mm 的既约剩余系的一个子集,我们还需要证明模 mm 的既约剩余系是 SS 的一个子集。
若 gcd(x1,m1)=gcd(x2,m2)=1gcd(x1,m1)=gcd(x2,m2)=1 ,又 gcd(m1,m2)=1gcd(m1,m2)=1 ,因此 gcd(m2x1,m1)=gcd(m1x2,m2)=1gcd(m2x1,m1)=gcd(m1x2,m2)=1 ,所以 gcd(m2x1+m1x2,m1)=gcd(m1x2+m2x1,m2)=1gcd(m2x1+m1x2,m1)=gcd(m1x2+m2x1,m2)=1 ,于是 gcd(m1x2+m2x1,m1m2)=1gcd(m1x2+m2x1,m1m2)=1 ,即 SS 是模 mm 的既约剩余系的子集。
因为 gcd(m1,m2)=1gcd(m1,m2)=1 ,因此 m2x1+m1x2m2x1+m1x2 可以表达所有整数。那么令模 mm 的既约剩余系的元素为 m2x1+m1x2m2x1+m1x2 ,则 gcd(m2x1+m1x2,m1m2)=1gcd(m2x1+m1x2,m1m2)=1 ,因此 gcd(m2x1+m1x2,m1)=gcd(m2x1+m1x2,m2)=1gcd(m2x1+m1x2,m1)=gcd(m2x1+m1x2,m2)=1 ,所以 gcd(m2x1,m1)=gcd(m1x2,m2)=1gcd(m2x1,m1)=gcd(m1x2,m2)=1 ,又 gcd(m1,m2)=1gcd(m1,m2)=1 ,于是 gcd(x1,m1)=gcd(x2,m2)=1gcd(x1,m1)=gcd(x2,m2)=1 ,即模 mm 的既约剩余系是 SS 的子集。
综上 SS 就是模 mm 的既约剩余系。
推论1的证明:
由性质4,可得 SS 的元素个数是 φ(m1)φ(m2)φ(m1)φ(m2) ,而模 mm 的既约剩余系的元素个数是 φ(m1m2)φ(m1m2) 。因此,当 gcd(m1,m2)=1gcd(m1,m2)=1 , φ(m1m2)=φ(m1)φ(m2)φ(m1m2)=φ(m1)φ(m2) ,即欧拉函数是积性函数。
欧拉函数
欧拉函数的定义与基本性质
定义 [1,n][1,n] 中与 nn 互质的个数记作 φ(n)φ(n) ,称为欧拉函数。
性质1 φ(p)=p−1φ(p)=p−1 ,其中 pp 为素数。
性质2 φ(pk)=pk−pk−1φ(pk)=pk−pk−1 ,其中 k∈Z+k∈Z+ , pp 为素数。
性质3 欧拉函数是积性函数,即 gcd(a,b)=1⇒φ(ab)=φ(a)⋅φ(b)gcd(a,b)=1⇒φ(ab)=φ(a)⋅φ(b) 。
性质4(欧拉函数的展开式) φ(n)=n∏p∣n(1−1p)φ(n)=n∏p∣n(1−1p) 。
性质5 p∣n 且 p2∤n⇒φ(n)=(p−1)φ(np)p∣n 且 p2∤n⇒φ(n)=(p−1)φ(np) ,其中 pp 为素数。
性质6 p2∣n⇒φ(n)=pφ(np)p2∣n⇒φ(n)=pφ(np) ,其中 pp 为素数。
性质4的证明:
由性质3直接可得
φ(n)=φ(k∏i=1pcii)=k∏i=1φ(pcii)=k∏i=1(pcii−pci−1i)=n∏p∣n(1−1p)φ(n)=φ(k∏i=1pcii)=k∏i=1φ(pcii)=k∏i=1(pcii−pci−1i)=n∏p∣n(1−1p)其中 pp 是素数。
性质3是由同余类部分证明得到的,比较困难。实际上,我们还能使用容斥原理直接证明,更加简单。
由此还能得出性质5和6。
欧拉函数的求法
试除法
利用试除法的分解质因子,通过性质4累乘即可,适用于只求单个欧拉函数的值。
可以提前 O(n)O(n) 预处理素数,时间复杂度 O(√nlnn)O(√nlnn) ,空间复杂度 O(n)O(n) 。
时间复杂度 O(√n)O(√n)
空间复杂度 O(1)O(1)
int one_euler(int n) { int ans = n; for (int i = 2;i * i <= n;i++) { if (!(n % i)) { ans = ans / i * (i - 1); while (!(n % i)) n /= i; } } if (n > 1)ans = ans / n * (n - 1); return ans; }
线性筛求欧拉函数
对于素数 pp ,直接令 φ(p)=p−1φ(p)=p−1 。
对于合数 nn ,因为只会在 i=npi=np 时被最小质因子 pp 筛一次,因此我们可以递推,但需要分类讨论:
- pp 比 ii 的最小质因子小,满足性质5,因此 φ(n)=φ(p)⋅φ(i)=(p−1)φ(i)φ(n)=φ(p)⋅φ(i)=(p−1)φ(i) 。
- pp 是 ii 的最小质因子,满足性质6, φ(n)=pφ(i)φ(n)=pφ(i) 。
时间复杂度 O(n)O(n)
空间复杂度 O(n)O(n)
const int N = 1e7 + 7; bool vis[N]; vector<int> prime; int phi[N]; void get_euler(int n) { phi[1] = 1; for (int i = 2;i <= n;i++) { if (!vis[i]) { prime.push_back(i); phi[i] = i - 1; } for (auto j : prime) { if (i * j > n) break; vis[i * j] = 1; if (!(i % j)) { phi[i * j] = j * phi[i]; break; } phi[i * j] = (j - 1) * phi[i]; } } }
欧拉函数的其他性质
性质1 当 n≥3n≥3 时,φ(n)φ(n) 为偶数。
性质2 f(n)=n∑i=1[gcd(i,k)=1]=⌊nk⌋φ(n)+f(nmodk)f(n)=n∑i=1[gcd(i,k)=1]=⌊nk⌋φ(n)+f(nmodk) 。
性质3 n∑i=1i[gcd(i,n)=1]=nφ(n)+[n=1]2n∑i=1i[gcd(i,n)=1]=nφ(n)+[n=1]2 。
性质4 ∑d∣nφ(d)=n∑d∣nφ(d)=n 。
性质5 φ(ab)=φ(a)⋅φ(b)⋅gcd(a,b)φ(gcd(a,b))φ(ab)=φ(a)⋅φ(b)⋅gcd(a,b)φ(gcd(a,b)) 。
性质6 d2∣n⇒φ(n)=dφ(nd)d2∣n⇒φ(n)=dφ(nd) 。
- 推论1(性质5、6的推论) 若 n=abdk,k≥2n=abdk,k≥2 且 gcd(a,b)=1gcd(a,b)=1 ,则 φ(n)=dk−2φ(abd2)=dk−2φ(ad)⋅φ(bd)⋅dφ(d)φ(n)=dk−2φ(abd2)=dk−2φ(ad)⋅φ(bd)⋅dφ(d) 。
性质1的证明:
对于 n≥3n≥3 ,当 m<nm<n ,若 gcd(m,n)=1gcd(m,n)=1 ,则 gcd(n−m,n)=1gcd(n−m,n)=1 ,同时 m≠n−mm≠n−m 。
因此每一个与 nn 互质的数 mm 都对应一个 n−mn−m 也与 nn 互质,所以 φ(n)φ(n) 为偶数。
性质2的证明:
f(n)f(n) 等同于 [1,n][1,n] 中与 kk 互质的数的个数。
我们把 [1,n][1,n] 每 kk 个数分一段,能分完整的 ⌊nk⌋⌊nk⌋ 段,以及 nmodknmodk 个多出来的数。
根据 gcd(a,b)=gcd(amodb,b)gcd(a,b)=gcd(amodb,b) ,因此 [1+mk,k+mk][1+mk,k+mk] 中与 kk 互质的数等于 [1,k][1,k] 中与 kk 互质的数,因此完整的 ⌊nk⌋⌊nk⌋ 段中,共有 ⌊nk⌋φ(k)⌊nk⌋φ(k) 个与 kk 互质的数。而 [1+⌊nk⌋k,n][1+⌊nk⌋k,n] 中与 kk 互质的数等于 [1,nmodk][1,nmodk] 中与 kk 互质的数,即 f(nmodk)f(nmodk) 。
综上 f(n)=n∑i=1[gcd(i,k)=1]=⌊nk⌋φ(n)+f(nmodk)f(n)=n∑i=1[gcd(i,k)=1]=⌊nk⌋φ(n)+f(nmodk) 。
性质3的证明:
左式等同于 [1,n][1,n] 中与 nn 互质的数的和。
当 n≥3n≥3 时,根据性质 11 ,互质的数一定形如 m,n−mm,n−m 成对出现,因此总和的平均值为 n2n2 ,因此总和为 nφ(n)2nφ(n)2 。
当 n=2n=2 时,上式也成立。
当 n=1n=1 时,上式结果为 1212 ,因此增加修正 [n=1]2[n=1]2 。
综上, n∑i=1i[gcd(i,n)=1]=nφ(n)+[n=1]2n∑i=1i[gcd(i,n)=1]=nφ(n)+[n=1]2 。
性质4的证明:
令 f(x)=n∑i=1[gcd(i,n)=x]f(x)=n∑i=1[gcd(i,n)=x] ,则 n∑i=1f(i)=nn∑i=1f(i)=n ,这是因为每个数的 gcdgcd 唯一,遍历一遍 gcdgcd 能覆盖 nn 。
又 gcd(i,n)=d⟺gcd(id,nd)=1gcd(i,n)=d⟺gcd(id,nd)=1 ,所以当 x∣nx∣n 时, f(x)=nx∑i=1[gcd(i,nx)=1]=φ(nx)f(x)=nx∑i=1[gcd(i,nx)=1]=φ(nx) ;当 x∤nx∤n 时, f(x)=0f(x)=0 。
综上 ∑d∣nφ(d)=∑d∣nφ(nd)=∑d∣nf(d)=n∑i=1f(i)=n∑d∣nφ(d)=∑d∣nφ(nd)=∑d∣nf(d)=n∑i=1f(i)=n 。
性质5的证明:
根据基本性质5可得
φ(ab)=ab∏p∣ab(1−1p)=a∏p∣a(1−1p)⋅b∏p∣b(1−1p)∏p∣a∩p∣b(1−1p)=φ(a)⋅φ(b)⋅gcd(a,b)gcd(a,b)∏p∣gcd(a,b)(1−1p)=φ(a)⋅φ(b)⋅gcd(a,b)φ(gcd(a,b))φ(ab)=ab∏p∣ab(1−1p)=a∏p∣a(1−1p)⋅b∏p∣b(1−1p)∏p∣a∩p∣b(1−1p)=φ(a)⋅φ(b)⋅gcd(a,b)gcd(a,b)∏p∣gcd(a,b)(1−1p)=φ(a)⋅φ(b)⋅gcd(a,b)φ(gcd(a,b))
性质6的证明:
因为 d2∣nd2∣n ,所以 nn 与 ndnd 的质因子相同,因此 φ(n)=n∏p∣n(1−1p)=d⋅nd∏p∣nd(1−1p)=dφ(nd)φ(n)=n∏p∣n(1−1p)=d⋅nd∏p∣nd(1−1p)=dφ(nd) 。
同余重要定理
费马小定理
定理1(费马小定理) 若 pp 为质数且整数 aa 不是 pp 的倍数,则 ap−1≡1(modp)ap−1≡1(modp) 。
- 推论1(定理1的推论,费马降幂) 若 pp 为质数且整数 aa 不是 pp 的倍数,则 an≡anmod(p−1)(modp)an≡anmod(p−1)(modp) 。
定理2(费马大定理) 若整数 n≥3n≥3 ,则 xn+yn=znxn+yn=zn 无正整数解。
定理1的证明:
由欧拉定理可得, φ(p)=p−1φ(p)=p−1 ,且 gcd(a,p)=1gcd(a,p)=1 ,因此 ap−1≡1(modp)ap−1≡1(modp) 。
欧拉定理见下一节。
定理2的证明:
我有一个绝妙的证明方法,但这里地方太小了我写不下qwq。
欧拉定理
定理1(欧拉定理) 若 gcd(a,m)=1gcd(a,m)=1,则 aφ(m)≡1(modm)aφ(m)≡1(modm) 。
- 推论1(定理1的推论) gcd(a,m)=1⟺∃x∈Z+,axmodm=1gcd(a,m)=1⟺∃x∈Z+,axmodm=1 。
定理2(拓展欧拉定理,欧拉降幂)
定理1的证明:
设 {x1,x2,⋯,xφ(m)}{x1,x2,⋯,xφ(m)} 是一个模 mm 的既约剩余系。
根据同余类与剩余系基本性质3,因为 gcd(a,m)=1gcd(a,m)=1 ,所以 {ax1,ax2,⋯,axφ(m)}{ax1,ax2,⋯,axφ(m)} 也是模 mm 的一个既约剩余系。因此, x1x2⋯xφ(m)≡aφ(m)x1x2⋯xφ(m)(modm)x1x2⋯xφ(m)≡aφ(m)x1x2⋯xφ(m)(modm) 。
又 gcd(x1,m)=⋯=gcd(xφ(m),m)=1gcd(x1,m)=⋯=gcd(xφ(m),m)=1 ,所以 gcd(x1x2⋯xφ(m),m)=1gcd(x1x2⋯xφ(m),m)=1 ,于是 aφ(m)≡1(modm)aφ(m)≡1(modm) 。
推论1的证明:
从左到右,由条件 ∃x∈N+,gcd(ax,m)=gcd(axmodm,m)=gcd(1,m)=1∃x∈N+,gcd(ax,m)=gcd(axmodm,m)=gcd(1,m)=1 ,所以 gcd(ax,m)=gcd(a,m)=1gcd(ax,m)=gcd(a,m)=1 。
从右到左,根据欧拉定理显然。
定理2的证明:
威尔逊定理
pp 修正阶乘的定义 设 pp 为素数,去除 n!n! 能被 pp 整除的数字的剩下部分,即 n∏i=1,(i,p)=1in∏i=1,(i,p)=1i ,称为 nn 的 pp 修正阶乘,记作 (n!)p(n!)p 。
定理1 若 pp 为素数, nn 为正整数,则 n!=(n!)p⌊n/p⌋!p⌊n/p⌋n!=(n!)p⌊n/p⌋!p⌊n/p⌋ 。
定理2 若 pp 为素数, nn 为正整数,则 n!pvp(n!)=⌊n/p⌋!pvp(⌊n/p⌋!)(n!)pn!pvp(n!)=⌊n/p⌋!pvp(⌊n/p⌋!)(n!)p 。
定理3 若 pp 为素数, n,αn,α 为正整数,则 (n!)p≡(pα!)⌊n/pα⌋p((nmodpα)!)p(modpα)(n!)p≡(pα!)⌊n/pα⌋p((nmodpα)!)p(modpα) 。
定理4(威尔逊定理) 当且仅当 pp 为素数, (p−1)!≡−1(modp)(p−1)!≡−1(modp) 。
定理5(扩展威尔逊定理) 若 pp 为素数, αα 为正整数,那么 (pα!)p≡{1,p=2 and α≥3−1,otherwise(modpα)(pα!)p≡{1,p=2 and α≥3−1,otherwise(modpα) 。
- 推论1(定理2、3、5的推论) 若 pp 为素数, n,αn,α 为正整数, n!pvp(n!)≡(±1)⌊n/pα⌋⌊n/p⌋!pvp(⌊n/p⌋!)((nmodpα)!)p(modpα)n!pvp(n!)≡(±1)⌊n/pα⌋⌊n/p⌋!pvp(⌊n/p⌋!)((nmodpα)!)p(modpα) 。
定理1的证明:
n!=(p−1)!×p×(2p−1)!(p)!×2p×⋯×(⌊n/p⌋p−1)!((⌊n/p⌋−1)p)!×⌊n/p⌋p×n!(⌊n/p⌋p)!n!=(p−1)!×p×(2p−1)!(p)!×2p×⋯×(⌊n/p⌋p−1)!((⌊n/p⌋−1)p)!×⌊n/p⌋p×n!(⌊n/p⌋p)! ,因此 n!=(n!)p⌊n/p⌋!p⌊n/p⌋n!=(n!)p⌊n/p⌋!p⌊n/p⌋ 。
定理2的证明:
根据定理1可以得到 n!=(n!)p⌊n/p⌋!p⌊n/p⌋n!=(n!)p⌊n/p⌋!p⌊n/p⌋ ,再根据勒让德定理的推论1可以得到 vp(n!)=⌊n/p⌋+vp(⌊n/p⌋!)vp(n!)=⌊n/p⌋+vp(⌊n/p⌋!) ,因此有 n!pvp(n!)=n!p⌊n/p⌋pvp(⌊n/p⌋!)=⌊n/p⌋!pvp(⌊n/p⌋!)(n!)pn!pvp(n!)=n!p⌊n/p⌋pvp(⌊n/p⌋!)=⌊n/p⌋!pvp(⌊n/p⌋!)(n!)p 。
定理3的证明:
我们可以以 pαpα 为一个周期划分 (n!)p(n!)p ,可以发现共有 ⌊npα⌋⌊npα⌋ 部分且都同余第一部分 (pα!)p(pα!)p ,剩下的同余 ((nmodpα)!)p((nmodpα)!)p ,因此可以得到 (n!)p≡(pα!)⌊n/pα⌋p((nmodpα)!)p(modpα)(n!)p≡(pα!)⌊n/pα⌋p((nmodpα)!)p(modpα) 。
定理4的证明:
充分性:
若 pp 不是素数,分类讨论:
- 当 p=4p=4 时, (4−1)!≡2(mod4)(4−1)!≡2(mod4) ,显然不成立。
- 当 p=ab(a≠b)p=ab(a≠b) 时,由于 a,b<pa,b<p ,所以 (p−1)!≡kab≡0(modp)(p−1)!≡kab≡0(modp) 。
- 当 p=a2(a>2)p=a2(a>2) 时,有 a,2a<a⋅a=pa,2a<a⋅a=p ,所以 (p−1)!=2ka2≡0(modp)(p−1)!=2ka2≡0(modp) 。
所以 pp 必为素数。
必要性:
若 pp 为素数,则 [1,p−1][1,p−1] 的整数必存在唯一逆元。因此,对于 x∈[1,p−1]x∈[1,p−1] ,其逆元 x−1x−1 若不等于 x ,则可以相互配对可得 x⋅x−1≡1(modp) ,考虑 x2≡1(modp) 的情况。
设任意 x,y∈[1,p−1] 且 x≠y ,满足 x2≡y2≡1(modp) ,那么有 x2−y2≡(x+y)(x−y)≡0(modp) ,其中一定有 p∤x−y ,于是 p∣x+y ,所以一定有 x+y=p ,显然只有 1,p−1 满足。
因此, (p−1)!≡p−1≡−1(modp) 。
综上得证。
定理5的证明:
与威尔逊定理必要性证明类似,即配对逆元,再考虑 x2≡1(modpα) 解的情况。
我们对 x2≡1(modpα) 解的情况分类讨论:
- 当 p=2 且 α=1 时,仅有一解 1 。
- 当 p=2 且 α≤3 时,有四解 ±1,2q−1±1 。
- 其余情况,均有两解 ±1 。
因此可以得到 (n!)pmodpq 的所有情况。
推论1的证明:
由定理2可得 n!pvp(n!)=⌊n/p⌋!pvp(⌊n/p⌋!)(n!)p ,再有定理3可得 (n!)p≡(pα!)⌊n/pα⌋p((nmodpα)!)p(modpα) ,最后根据定理5可得 (pα!)p≡±1(modpα) 可根据具体情况判断,最后 n!pvp(n!)≡(±1)⌊n/pα⌋⌊n/p⌋!pvp(⌊n/p⌋!)((nmodpα)!)p(modpα) 。
二元一次不定方程
二元一次不定方程的定义与基本性质
定义 关于整数 x,y 的方程 ax+by=c ,其中 a,b,c 都是整数且 a,b 不为 0 ,称为二元一次不定方程。
裴蜀定理
定理1(裴蜀定理) 关于整数 x,y 不定方程 ax+by=c 有解的充要条件是 gcd(a,b)∣c 。
- 推论1 关于整数 x,y 不定方程 ax+by=1 有解的充要条件是 gcd(a,b)=1 。
定理2 关于整数 x,y 不定方程 ax+by=c ,满足 d=gcd(a,b),d∣c ,的所有解为
其中 (x0,y0) 是方程的特解, k∈Z 。
定理1的证明:
设 d=gcd(a,b) ,则 d∣ax+by ,设 s 为 ax+by 的最小正值。
根据带余数除法,可得 a=qs+r ,则 r=a−qs=a−q(ax+by)=a(1−qx)+b(−qy) ,即 r 也可以被 ax+by 表示。
因为 r∈[0,s−1] ,所以 r=0 ,即 s∣a ,同理 s∣b 。因此,s∣d ,所以 s≤d 。
因为 s=ax+by ,所以 d∣s , 于是 d≤s 。
综上 d=s ,即 gcd(a,b)=d 是 ax+by 的最小正值。
定理2的证明:
有特解 (x0,y0) ,设任意解为 (x,y) ,则 ax0+by0=ax+by=c ,于是有 a(x−x0)=b(y0−y) 。两边同时除以 d ,得到 ad(x−x0)=bd(y0−y) ,其中 gcd(ad,bd)=1 ,于是 ad∣y0−y,bd∣x−x0 。因此有
{x=x0+kbdy=y0−kad其中 k∈Z 。
解二元一次不定方程
从数学角度,二元一次不定方程系数是整数范围的,但使用算法时我们需让 a,b≥0 ,详见整除章节 gcd 的求法前言。若出现如 a<0 等系数为负数情况,需转换为 |a|(−x)+by=gcd(a,b) 求解。以下的证明建立在正系数之上。
扩展欧几里得算法
对于二元一次不定方程的求解,关键在于求出一组特解。扩展欧几里得算法(extended gcd,exgcd)在辗转相除法的基础上,递归求解关于 x,y 的方程 ax+by=gcd(a,b) 的一组特解。
证明:
考虑方程 ax+by=gcd(a,b)=gcd(b,a−b⌊ab⌋)=bx′+(a−b⌊ab⌋)y′ 。
我们可以得到 ax+by=ay′+b(x′−⌊ab⌋y′) ,于是得到
{x=y′y=x′−⌊ab⌋y′递归至 b=0 ,此时 ax+0y=gcd(a,0)=a ,易得 x=1,y=0 是一组解,就可以回溯求解。
通过exgcd求出一组特解以后,即可通过裴蜀定理中的定理2得到所有解。
可以证明exgcd的解 (x,y) 满足 |x|≤b,|y|≤a ,所以在 int
范围内可以都用 int
变量,详见OI-wiki。
以下只提供exgcd的算法。
时间复杂度 O(log(min{a,b}))
空间复杂度 O(1)
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); x -= (a / b) * y, swap(x, y); return d; }
二元一次不定方程的非负整数解
我们仅讨论 a,b,c≥1 且 gcd(a,b)=1 的情况下,方程 ax+by=c 的非负整数解,即 x,y≥0 的解。
性质1 若 c>ab−a−b ,则 ax+by=c 存在非负整数解。
性质2 若 c=ab−a−b ,则 ax+by=c 不存在非负整数解。
性质3 若 c<ab−a−b ,则 ax+by=c 恰好有 max{0,(a−1)(b−1)2−2} 个 c 有非负整数解,且解唯一。
性质1的证明:
对于 x∈[0,b−1] ,by=c−ax>ab−a−b−ax≥ab−a−b−a(b−1)=−b ,所以 y≥0 。
所以方程有非负解。
性质2的证明:
ax+by=ab−a−b ,即 a(x+1)+b(y+1)=ab 。因为 gcd(a,b)=1 ,所以 a∣y+1,b∣x+1 。
设 ka=y+1,mb=x+1 ,其中 k,m∈Z+ 。代入原式得, (m+k)ab=ab ,所以 m+k=1 ,所以必有一个小于等于 0 ,即 x,y 中必有一个小于 0 ,因此没有非负解。
性质3的证明:
分三步证明。
若 ax+by=c 有非负解,则解 (x,y)∈[0,b−2]×[0,a−2] 且 (x,y)≠(0,0),(b−2,a−2) 。
因为 y≥0 ,所以 ax=c−by≤c<ab−a−b ,所以 x<b−1−ba ,即 x≤b−2 。
同理 y≤a−2 。
(x,y)≠(0,0),(b−2,a−2) 可以验证得到。
若 ax+by=c 有非负解,则最多只有一个非负解。
若 (x0,y0) 是一个非负解,则 x0∈[0,b−2],y0∈[0,a−2] 。因为 gcd(a,b)=1 ,所以通解为
{x=x0+kby=y0−ka其他解一定不在 [0,b−2]×[0,a−2] 中,一定不是解,因此只有一个非负解。
当 (x,y)∈[0,b−2]×[0,a−2] 且 (x,y)≠(0,0),(b−2,a−2) , (x,y) 是 ax+by=c 的一个非负解,当且仅当 (b−2−x,a−2−y) 不是任何 c<ab−a−b 的非负解。
必要性:
ax+by=c<ab−a−b ,所以
a(b−2−x)+b(a−2−y)=ab−2a−ax+ab−2b−by=2(ab−a−b)−(ax+by)>ab−a−b因此 (b−2−x,a−2−y) 不是任何 c<ab−a−b 的非负解。
充分性:
因为 (b−2−x,a−2−y) 不是任何 c<ab−a−b 的非负解,且 a(b−2−x)+b(a−2−y)>0 以及 c=ab−a−b 没有非负解,因此 a(b−2−x)+b(a−2−y)>ab−a−b ,所以
a(b−2−x)+b(a−2−y)=ab−2a−ax+ab−2b−by=2(ab−a−b)−(ax+by)>ab−a−b于是 ax+by<ab−a−b ,又 ax+by>0 ,所以 (x,y) 一定是 c<ab−a−b 的一个非负解。
综上,当 c<ab−a−b 时,
由1得知,解一定出现在 (x,y)∈[0,b−2]×[0,a−2] 且 (x,y)≠(0,0),(b−2,a−2) 。
由3得知,有解和无解一定成对出现,所以共 max{0,(a−1)(b−1)2−2} 个解。
由2得知,解和 c 是一一对应的,所以共 max{0,(a−1)(b−1)2−2} 个 c 有解。
乘法逆元
乘法逆元的定义与基本性质
定义 当且仅当 gcd(a,m)=1 时,存在整数 x 满足 ax≡1(modm) ,我们称 x 为 a 在模 m 意义下的逆元,记作 a−1 。
乘法逆元的求法
费马小定理法
若 m 为质数且整数 a 不是 m 的倍数,则 am−1≡a⋅am−2≡1(modm) ,其中 am−2 即为所求逆元。
时间复杂度 O(logm)
空间复杂度 O(1)
const int P = 1e9 + 7; int qpow(int a, ll k) { int ans = 1; while (k) { if (k & 1) ans = 1LL * ans * a % P; k >>= 1; a = 1LL * a * a % P; } return ans; } int inv(int x) { return qpow(x, P - 2); }
扩展欧几里得算法
若 gcd(a,m)=1 ,那么 ax≡1(modm)⟺ax+my=1 一定存在解,即一定有 a 在模 m 意义下的逆元 a−1=x 。因此,我们可以解 ax+my=1 ,利用exgcd可以直接求解。
时间复杂度 O(log(min{a,m}))
空间复杂度 O(1)
const int P = 1e9 + 7; 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); x -= (a / b) * y, swap(x, y); return d; } int inv(int a) { int x, y; exgcd(a, P, x, y); return (x % P + P) % P; }
线性递推求乘法逆元
若需要频繁使用 [1,n] 内模 m 的逆元,其中 n<m 且 m 为质数,可以考虑预处理 [1,n] 的所有逆元。枚举每个数字朴素求逆元是线性对数复杂度的,但事实上存在一种递推的求法,可以在线性复杂度内从 1 递推出 [1,n] 的所有逆元。
证明:
当 i=1 时,我们有 1⋅1≡1(modm) ,因此 1−1=1 。
当 i>1 时,考虑 m=⌊mi⌋⋅i+mmodi=pi+r ,于是有
m≡0(modm)pi+r≡0(modm)pr−1+i−1≡0(modm)i−1≡−pr−1(modm)i−1≡−⌊mi⌋⋅(mmodi)−1(modm)因此,我们可以从 1≤mmodi<i 的逆元推出 i 的逆元。
在这里我们使用 (m−⌊mi⌋)⋅(mmodi)−1 防止出现负数。
时间复杂度 O(n)
空间复杂度 O(n)
const int P = 1e9 + 7; const int N = 1e7 + 7; int inv[N]; void get_inverse(int n) { inv[1] = 1; for (int i = 2;i <= n;i++) inv[i] = 1LL * (P - P / i) * inv[P % i] % P; }
线性递推求阶乘逆元
通常在组合数学中需要频繁 i!,i∈[1,n] 模 m 的逆元,其中 n<m 且 m 为质数,我们同样也可以线性递推。我们可以线性求出 [1,n] 的逆元后再求阶乘的逆元,也可以从 (n!)−1 倒推。我们采用倒推的方法。
证明:
已知 (n!)−1 ,可以递推求出 n! 后,用费马小定理或exgcd求解 (n!)−1 。
当 1≤i<n 时,有 (i!)−1≡((i+1)!)−1⋅(i+1)(modm) 。
因此,我们可以从 (i+1)! 的逆元推出 i! 的逆元。
时间复杂度 O(n)
空间复杂度 O(n)
const int P = 1e9 + 7; const int N = 1e7 + 7; int qpow(int a, ll k) { int ans = 1; while (k) { if (k & 1) ans = 1LL * ans * a % P; k >>= 1; a = 1LL * a * a % P; } return ans; } int fact[N], invfact[N]; void get_inverse(int n) { fact[0] = 1; for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P; invfact[n] = qpow(fact[n], P - 2); for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P; }
一元一次同余方程
一元一次同余方程的定义与基本性质
定义 关于整数 x 的方程 ax≡b(modm) ,其中 a,b 为整数, m 为正整数,称为一元一次同余方程。
约定 关于整数 x 的方程 ax≡b(modm) ,我们认为在模 m 下同余的解 x 是同一个解。
解数的定义 方程不同的解的个数为方程的解数。
性质1 关于整数 x 的一元一次同余方程 ax≡b(modm) ,等价于关于整数 x,y 的二元一次不定方程 ax+my=b ,其中 x 意义相同。因此,二元一次不定方程的性质与定理可以直接用于一元一次同余方程。
性质2 关于整数 x 的一元一次同余方程 ax≡b(modm) ,其解数为 gcd(a,m) 。
性质2的证明:
通过性质1转换成二元一次不定方程,可求方程的所有解,容易发现解数为 gcd(a,m) 。
解一元一次同余方程
扩展欧几里得算法
关于整数 x 的同余方程 ax≡b(modm) ,根据性质1,可以转化为关于整数 x,y 的不定方程 ax+my=b ,于是同前面所说的不定方程的解法一致。
时间复杂度 O(log(min{a,m}))
空间复杂度 O(1)
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); x -= (a / b) * y, swap(x, y); return d; }
一元一次同余方程组
一元一次同余方程组的定义与基本性质
定义 关于整数 x 的方程组
其中 ai,bi(1≤i≤k) 为整数,mi(1≤i≤k) 为正整数,称为一元一次同余方程组。
约定 关于整数 x 的方程组 aix≡bi(modmi)(1≤i≤k) ,我们认为在模 M=lcm(m1,m2,⋯,mk) 下同余的解 x 是同一个解。
解数的定义 方程组不同的解的个数为方程组的解数。
性质1 对于任意关于 x 的一元一次同余方程组
等价于如下系数为 1 的方程组
其中 ci(1≤i≤k) 为第 i 个方程 aix≡bi(modmi) 的解数, ai,j(1≤i≤k,1≤j≤ci) 为第 i 个方程 aix≡bi(modmi) 的第 j 个解。
性质2 对于有如下形式的一元一次同余方程组
其解 x 与 M=lcm(m1,m2,⋯,mk) 满足 gcd(ai,mi)=1(1≤i≤k)⟺gcd(x,M)=1 。
性质1的证明:
对方程组中每个一元一次同余方程求解,可得对应方程的解集。显然,解集方程与原方程是同解的,可以等价替换原方程。
性质2的证明:
解 x 满足 x≡ai(modmi)(1≤i≤k) , 根据同余基本性质10,有 gcd(x,mi)=gcd(ai,mi) 。
随后,结合 gcd 基本性质6,有 gcd(ai,mi)=gcd(x,mi)=1⟺gcd(x,M)=1 ,因此得证。
中国剩余定理(孙子定理)
定理1(中国剩余定理) 对于有如下形式且满足 m1,m2,⋯,mk 两两互质的一元一次同余方程组
必有解,且解数为 1 。
定理1的证明:
设 M=lcm(m1,⋯,mk)=k∏i=1mi , Mi=Mmi , Mi 在模 mi 下的必有逆元为 M−1i 。
所以,我们可以构造出 x=k∑i=1aiMiM−1i ,容易证明 x 是符合方程组的一个解。
同时,对于任意两个解 x1,x2 ,我们有 x1≡x2(modmi)(i=1,⋯,k) ,根据同余基本性质9,可得 x1≡x2(modM) 。因此,解在模 M=lcm(m1,⋯,mk)=k∏i=1mi 下是唯一的,即解数为 1 。
换句话说,如此方程在 [0,M) 中的必有解且唯一,即其最小正整数解 xmin 。
解一元一次同余方程组
中国剩余定理
对于一般的一元一次同余方程组,都可以化为有如下形式(即系数为 1 )的同余方程组
若方程组还满足 m1,m2,⋯,mk 两两互质,则可以用中国剩余定理(Chinese Remainder Theorem, CRT)证明中的构造法求解。
时间复杂度 O(klog(max{mi}))
空间复杂度 O(k)
ll exgcd(ll a, ll b, ll &x, ll &y) { if (!b) { x = 1, y = 0; return a; } ll d = exgcd(b, a % b, x, y); x -= (a / b) * y, swap(x, y); return d; } ll inv(ll a, ll P) { ll x, y; exgcd(a, P, x, y); return (x % P + P) % P; } ll CRT(const vector<ll> &a, const vector<ll> &p) { int k = a.size() - 1; ll P = 1, ans = 0; for (int i = 1;i <= k;i++) P *= p[i]; for (int i = 1;i <= k;i++) { ll Pi = P / p[i], invPi = inv(Pi, p[i]); (ans += (__int128_t)a[i] * Pi * invPi % P) %= P; } return ans; }
扩展中国剩余定理
若一般的一元一次同余方程组化为的同余方程组
其模数 m1,m2,⋯,mk 不满足两两互质,那么CRT是无法解决的。我们就需要使用扩展中国剩余定理(extended CRT,exCRT),虽说名字叫exCRT,但和CRT没有任何关系,单纯是CRT失效的时候用的一种新算法。
exCRT面对这种情况,采用的是朴素合并方程的方法。在对方程进行 k−1 次合并后,得到最后一个方程,即方程组的解。
证明:
先考虑合并两个方程
{x≡a1(modm1)x≡a2(modm2)根据一元一次同余方程基本性质1,我们可以将方程组改写为二元一次不定方程组
{x=m1p1+a1x=m2p2+a2我们将两个方程相减可得 m1p1+m2(−p2)=a2−a1 ,根据裴蜀定理,当且仅当 gcd(m1,m2)∣a2−a1 时,该方程有解。
在有解的情况,通过exgcd可求出 m1p1+m2(−p2)=gcd(m1,m2) 的一组特解 p′1,−p′2 ,根据裴蜀定理的定理 2 得原方程的所有解为
{P1=p′1⋅a2−a1gcd(m1,m2)+km2gcd(m1,m2)−P2=(−p′2)⋅a2−a1gcd(m1,m2)−km1gcd(m1,m2)其中 k∈Z 。
将通解 P1 代入 x=m1p1+a1 ,则有
x=a1+m1p′1⋅a2−a1gcd(m1,m2)+km1m2gcd(m1,m2)=a1+m1p′1⋅a2−a1gcd(m1,m2)+k⋅lcm(m1,m2)其等价于同余方程 x≡a1+m1p′1⋅a2−a1gcd(m1,m2)(modlcm(m1,m2)) ,于是我们就合并了两个方程。
对于原方程组
{x≡a1(modm1)x≡a2(modm2)⋮x≡ak(modmk)只需要进行 k−1 次合并即可。
在实现的过程中,原方程的特解 p′1⋅a2−a1gcd(m1,m2) 可以模 m2gcd(m1,m2) (注意 a2−a1 可能是负数,但不影响exgcd求解,只影响 %
运算的结果),可以避免一部分数字溢出。
时间复杂度 O(klog(max{mi}))
空间复杂度 O(k)
ll exgcd(ll a, ll b, ll &x, ll &y) { if (!b) { x = 1, y = 0; return a; } ll d = exgcd(b, a % b, x, y); x -= (a / b) * y, swap(x, y); return d; } ll exCRT(const vector<ll> &a, const vector<ll> &p) { int k = a.size() - 1; ll ans = a[1], P = p[1]; for (int i = 2;i <= k;i++) { ll x, y; ll d = exgcd(P, p[i], x, y); ll c = a[i] - ans; if (c % d) return -1; ll pd = p[i] / d; x = ((__int128_t)c / d * x % pd + pd) % pd; ans += P * x; P *= pd; ans %= P; } return ans; }
特殊求和问题
问题描述
给定正整数 n 以及非负整数 a,b,c ,分别求
求解方式
类欧几里得算法
暂时不学。
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/17051072.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)