数论
数论
逆元
若 b,m 互质,并且 b|a,则存在一个整数 x 使得
则称 x 为 b 的模 m 乘法逆元,记作 b−1(modm)。
例如 b=31,m=5,a=93,存在 x=1 满足 9331≡93×1 (mod5)。
因为乘法满足模运算的分配律,即 (a×b)modm=((amodm)×(bmodm))modm。
而除法不满足,故乘法逆元就可以很好地解决这个问题,即
这里介绍三种求逆元的方法:
1.
因为
所以
其实也就是要找到一个 x,满足 b×x≡1(modm)。
当 m 为质数时,根据费马小定理 bm≡b(modm) 可得:b×bm−2≡1(modm)。
因此,当模数 m 为质数时,bm−2 就是 b 的模 m 乘法逆元。
2.
第一种方法说过,
其实也就是要找到一个 x,满足 b×x≡1(modm)。
也就是解 my+bx=1 这个方程,故我们可以使用扩展欧几里得来求解 x。
3.
这里介绍一个线性时间求 1∼n 在模 m 下的逆元。
我们设 m=q×d+r (r<d)。
可以得到;
- q×d+r≡0(modm),
- q=⌊md⌋
- r=mmodd
对于式子 1. 两边同乘 d−1×r−1,得到
故可以根据前面的 (mmodd)−1 推出后面的 d−1。
#include <bits/stdc++.h>
const int N = 3e6 + 10;
using namespace std;
int n, p;
long long ans[N];
int main()
{
cin >> n >> p;
ans[1] = 1;
printf("%lld\n", 1);
for (int i = 2; i <= n; ++ i)
ans[i] = ((- (p / i) * ans[p % i]) % p + p) % p,
printf("%lld\n", ans[i]);
return 0;
}
扩展欧几里得定理
问题引入
我们要解决一个这样的问题:
分析
在欧几里得算法的最后一步,即 b=0 时,显然有一对整数 x=1,y=0 满足 a×1+b×0=gcd(a,0)。
我们假设有 x2,y2 满足 bx2+(amodb)y2=gcd(b,amodb)。
由于 gcd(a,b)=gcd(b,amodb),故 ax+by=bx2+(amodb)y2
又因为 (amodb)=a−⌊ab⌋×b。
故
由于 a=a,b=b,故 x=y2,y=x2−⌊ab⌋y2,这样从 x=1,y=0 一步一步就可以推到 ax+by=gcd(a,b) 的解。
对于一般的方程 ax+by=c,当且仅当 gcd(a,b)|c 时有解。
我们可以先求出 ax+by=gcd(a,b) 的特殊解 x0,y0,然后令
此时 ax+by=c,x,y 可能为负数,有的题目会要求 x,y 为正整数,我们可以把原式进行修改,变为
即
其中 k∈Z。
类欧几里得算法
问题引入
设
其中 a,b,c,n 是常数,需要 O(logn) 的做法。
若 a≥c 或 b≥c,我们可以将 a,b 对 c 取模以简化问题。
考虑到
故
此时一定有 a<c 且 b<c。
令
再进行转化
考虑到
故
故
可以发现,上述式子是一个递归式,我们不断重复上述过程,先取模,后递归,其实就是辗转相除的过程,时间复杂度 O(logn)。
拓展
我们再来推导两个变种求和式
推导 g
引理 1.
证明如下:
考虑到
故
将这 n 个等式左右两边相加,得到
即
整理后得
首先和 f 一样,对其取模(根据引理 1. 可将其展开)。
其他部分推导与 f 类似。
令
有
令
则有
推导 h
同样套路,先取模
令
拆开则有
照样令
发现平方不好处理,将平方转化为加法
则
现在只要解决前面的式子即可
综上所述
当 a=0 时,上述三个式子都可以 O(1) 计算。
代码实现时,因为三个函数交错递归,考虑三个一起整体递归,同步计算,时间复杂度为 O(logn)。
#include <bits/stdc++.h>
const int mod = 998244353;
int InvTwo, InvSix;
inline int read()
{
int cnt = 0; char ch = getchar(); bool op = 1;
for (; ! isdigit(ch); ch = getchar())
if (ch == '-') op = 0;
for (; isdigit(ch); ch = getchar())
cnt = cnt * 10 + ch - 48;
return op ? cnt : - cnt;
}
inline int quick_pow(int a, int b)
{
int ret = 1;
for (; b; b >>= 1)
{
if (b & 1) ret = 1ll * ret * a % mod;
a = 1ll * a * a % mod;
}
return ret % mod;
}
struct eu
{
int f, g, h;
};
inline int S(int a, int b, int c, int i)
{
return ((1ll * a * i + b) / c) - 1;
}
inline eu solve(int a, int b, int c, int n)
{
if (a == 0)
{
eu now = {0, 0, 0};
now.f = 1ll * (b / c) * (n + 1) % mod;
now.h = 1ll * (b / c) * (b / c) % mod * (n + 1) % mod;
now.g = 1ll * n * (n + 1) % mod * InvTwo % mod * (b / c) % mod;
return now;
}
if (a >= c || b >= c)
{
eu now = {0, 0, 0};
now.f = (now.f + 1ll * n * (n + 1) % mod * InvTwo % mod * (a / c) % mod) % mod;
now.f = (now.f + 1ll * (n + 1) * (b / c) % mod) % mod;
now.g = (now.g + 1ll * n * (n + 1) % mod * (2 * n + 1) % mod * InvSix % mod * (a / c) % mod) % mod;
now.g = (now.g + 1ll * n * (n + 1) % mod * InvTwo % mod * (b / c) % mod) % mod;
now.h = (now.h + 1ll * n * (n + 1) % mod * (2 * n + 1) % mod * InvSix % mod * (a / c) % mod * (a / c) % mod) % mod;
now.h = (now.h + 1ll * (n + 1) * (b / c) % mod * (b / c) % mod) % mod;
now.h = (now.h + 1ll * n * (n + 1) % mod * (a / c) % mod * (b / c) % mod) % mod;
eu nxt = solve(a % c, b % c, c, n);
now.f = (now.f + nxt.f) % mod;
now.g = (now.g + nxt.g) % mod;
now.h = (now.h + nxt.h) % mod;
now.h = (now.h + 1ll * 2 * (a / c) % mod * nxt.g % mod) % mod;
now.h = (now.h + 1ll * 2 * (b / c) % mod * nxt.f % mod) % mod;
return now;
}
else
{
eu now = {0, 0, 0};
eu nxt = solve(c, c - b - 1, a, S(a, b, c, n));
now.f = 1ll * (S(a, b, c, n) + 1) * n % mod;
now.f = (now.f - nxt.f) % mod;
now.f = (now.f + mod) % mod;
now.g = 1ll * InvTwo * ((((1ll * (S(a, b, c, n) + 1) * n % mod * (n + 1) % mod - nxt.h) % mod) - nxt.f) % mod) % mod;
now.g = (now.g + mod) % mod;
now.h = (1ll * (S(a, b, c, n) + 1) * (S(a, b, c, n) + 2) % mod * n % mod) % mod;
now.h = (now.h - 1ll * 2 * nxt.g) % mod;
now.h = (now.h - 1ll * 2 * nxt.f) % mod;
now.h = (now.h - now.f) % mod;
now.h = (now.h + mod) % mod;
return now;
}
}
int main()
{
int t = read();
InvTwo = quick_pow(2, mod - 2);
InvSix = quick_pow(6, mod - 2);
while (t --)
{
int n, a, b, c;
n = read(), a = read(), b = read(), c = read();
eu ans = solve(a, b, c, n);
printf("%d %d %d\n", ans.f, ans.h, ans.g);
}
return 0;
}
Reference
中国剩余定理(CRT)
问题引入
我们需要解决满足下列方程组的一个解 x,并且保证所有的 b 都互相互质。
分析
我们考虑设 M=n∏i=1bi,mi=Mbi,ti 为 mix≡1(modbi) 的一个解,则解为:
我们现在来证明一下 x 是一个满足条件的解。
证明:
对于任意的 k∈[1,n],akmktk≡ak(modbk),
而 akmktk≡0(modbi)(i∈[1,k−1] ⋃ [k+1,n]),故 x 是一个满足条件的解。
我们知道,M≡0(modbi)(i∈[1,n]),故 x+kM (k∈Z) 都是满足答案的解,若题目要求最小正整数解,则:
#include <bits/stdc++.h>
const int N = 10 + 10;
using namespace std;
int n;
long long M = 1, m[N], t[N], ans;
int a[N], b[N];
inline void exgcd(long long a, long long b, long long &x, long long &y)
{
if (b == 0)
{
x = 1, y = 0;
return;
}
exgcd(b, a % b, x, y);
long long d = x; x = y; y = d - (a / b) * y;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; ++ i)
cin >> a[i] >> b[i], M = M * a[i];
for (int i = 1; i <= n; ++ i)
{
long long y;
m[i] = M / a[i];
exgcd(m[i], a[i], t[i], y);
ans = (ans + m[i] * t[i] * b[i]) % M;
}
cout << (ans + M) % M << endl;
return 0;
}
拓展中国剩余定理 (EXCRT)
问题引入
我们要解决的还是这么上文那个问题,其中不保证所有的 b 都互相互质。
分析
我们假设已经有一个解 x 满足前 k−1 个同余方程,此时我们设 d=lcm{b1,b2,⋯,bk−1},显然,x+jd (j∈Z) 都是满足条件的解。
那么对于第 k 个同余方程,即要满足 x+jd≡ak(modbk),可转换为 jd≡ak−x (mod bk),显然,可以用扩展欧几里得来求解 j,若 gcd(d,bk)∤ak−x 那么就无解,否则 x=x+jd,d=lcm{b1,b2,⋯,bk},此时的 x 是满足前 k 个方程的,这样一直做到最后就可以求出答案。
#include <bits/stdc++.h>
const int N = 1e5 + 10;
using namespace std;
int n; long long X, m;
long long a[N], b[N];
inline long long exgcd(long long a, long long b, long long &x, long long &y)
{
if (b == 0)
{
x = 1, y = 0;
return a;
}
long long d = exgcd(b, a % b, x, y);
long long t = x; x = y; y = t - (a / b) * y;
return d;
}
inline long long quick_mul(long long a, long long b, long long p)
{
a %= p, b %= p;
long long res = 0, op = 1;
if (b < 0) b = -b, op = -1;
for (; b; b >>= 1)
{
if (b & 1) res = (res + a) % p;
a = (a + a) % p;
}
return res * op;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i] >> b[i];
X = b[1], m = a[1];
for (int i = 2; i <= n; ++i)
{
long long d, x, y, c = (b[i] - X);
d = exgcd(m, a[i], x, y);
x = quick_mul(x, (c / d), (a[i] / d));
x = (x % (a[i] / d) + (a[i] / d)) % (a[i] / d);
X = (X + x * m);
m = m / d * a[i];
X = (X % m + m) % m;
}
cout << X << endl;
return 0;
}
BSGS
问题引入
我们需要解决 ax≡b(modM) 的一个解 x,其中 a,M 互质。
分析
首先,如果有解,那么解一定在 [0,M] 这个区间内,根据抽屉原理,axmodM 只会有 M 个取值,故 [0,M] 中至少会存在两个相同余数的数,即一定存在一对 i,j 满足 ai≡aj(modM),那么在 i 之后,aimod M,ai+1mod M,⋯ajmod M 这些数会循环出现,故如果有解,那么解一定在 [0,M] 这个区间内。
我们考虑把 x 进行拆分,x=qm−p,其中 m=⌊√x⌋+1,q∈[1,m],p∈[0,m]。
显然,qm−p 可以表示出 0∼m 中的所有数。
我们把原式修改一下得到 aqm−p≡b(modM),aqm≡b×ap(modM)(该步后往前推导需要有乘法逆元)
我们发现 q 与 p 没有关系了,考虑先 O(√x) 枚举 p,把 (b×ap)modM 放入哈希表(map 也行),然后再 O(√x) 枚举 aqm,找到最小的 q 满足 aqmmodM 在哈希表中出现过,那么 qm+p 就是最小的答案。
#include <bits/stdc++.h>
int p, b, n, m;
std::map <int, int> mp;
inline int quick_pow(int x, long long y)
{
int res = 1; x %= p;
for (; y; y >>= 1)
{
if (y & 1) res = 1ll * res * x % p;
x = 1ll * x * x % p;
}
return res;
}
int main()
{
std::cin >> p >> b >> n;
m = sqrt(p) + 1;
for (int i = 0; i <= m; ++ i)
mp[1ll * n * quick_pow(b, i) % p] = i;
for (int i = 1; i <= m; ++ i)
{
if (mp[quick_pow(b, 1ll * i * m)])
{
std::cout << 1ll * i * m - mp[quick_pow(b, i * m)] << std::endl;
return 0;
}
}
std::cout << "no solution" << std::endl;
return 0;
}
扩展 BSGS
问题引入
我们要解决的还是 ax≡b(modp) 这个问题,只是不满足 a 与 p 互质。
分析
若 a 与 p 互质,那么就会存在逆元,可以将 x 分解为 qm−p 来求解,所以我们要想办法让 a,p 变得互质。
我们考虑把这个式子转化为不定方程,ax−yp=b,首先,我们求出 d1=gcd(a,p),然后整个式子都除以 d1,若 b 与 d1 互质,那么就无解,否则我们继续求出 d2=gcd(a,p),一直进行下去,直到 a 与 p 互质。
假设进行了 k 次,最后得到式子就是:
其中 a 与 p 互质。
我们再化为同余方程的形式:
显然,求出
的逆元,然后直接移到右边,直接用 BSGS 来解决,然后最后把答案加上 k 即可。
注意,不保证答案小于 k 的情况,故在消因子前做一下 O(k) 枚举,直接验证 ai≡b(modp),就能避免这种情况。
高斯消元
问题引入
给出 n 组方程,每组方程形如 n∑i=1aixi=bi,要求求出 x1,x2⋯xn 或告知无解。
分析
我们把这些方程转化为矩阵
我们希望最后的矩阵形如
对于第 i 行就能求出 xi=ci,即可求出解。
流程如下:
- 我们从上往下做,先将当前系数绝对值最大的移到当前这一行,减小误差。
- 让当前系数化为 1,即让当前行的每一个数都除以当前系数。
- 再用当前第 i 行的第 i 个系数(即为 1)消去 j (j≠i) 的第 i 个系数,把它消为 0。
重复做以上的操作,直到做到第 n 行,此时最后的矩阵形如上述。
我们可以在 O(n3) 的时间复杂度完成。
判断解情况:
最后说一下如何判无解和无限解:
-
无解:当前行系数全为 0,但值不为 0。
-
无限解:当前行系数全为 0,且值为 0。
一、积性函数
1. 积性函数:
对于任意互质的整数 a 和 b (gcd(a,b)=1) 有性质 f(ab)=f(a)×f(b)。
2. 完全积性函数:
对于任意的整数 a 和 b 有性质 f(ab)=f(a)×f(b)。
3. 证明 φ(n) 是积性函数。
1. 定理:
当 gcd(x,y)=1 时,φ(x×y)=φ(x)×φ(y)。
2. 证明:
我们将 1∼x×y 排列成如下方阵:
由于 gcd(x,y)=1,每一列 i+j×y 都能构成一个 mody 的完全剩余系 {¯¯¯0,¯¯¯1,⋯,¯¯¯¯¯¯¯¯¯¯¯¯y−2,¯¯¯¯¯¯¯¯¯¯¯¯y−1},则每一列与 y 互质的数就有 φ(y)。同理,对于每一行,都能构成一个 modx 的完全剩余系 {¯¯¯0,¯¯¯1,⋯,¯¯¯¯¯¯¯¯¯¯¯¯¯x−2,¯¯¯¯¯¯¯¯¯¯¯¯¯x−1},并且它们都是对齐的,则每一行与 x 互质的数就有 φ(x)。
若 a 与 x×y 互质,则 a 与 x 互质且 a 与 y 互质,即 φ(x×y)=φ(x)×(y)。
二、欧拉函数
1. 欧拉函数的定义及求法
φ(x) 表示有多少个 i (1≤i≤x) 满足 gcd(i,x)=1(即有多少个 ≤x 的 i 与 x 互质)。
显然当 x 为质数时,φ(x)=x−1。
把 x 唯一分解:
有
我们来证明一下这个定理:
引理 1:设 x=pk (p 为质数),那么 φ(x)=pk−1×(p−1)。
证明 1:此时对于一个 y 与 x 互质,当且仅当 y≢0(modp),我们考虑将 x 分成许多段,长度为 p,有 xp=pkp=pk−1 段。其中每一段都有 p−1 个数与 p 互质,有 pk−1 段,故 φ(x)=pk−1×(p−1)。
根据欧拉函数的积性性质,我们可以得到:
故我们可以使用试除法在 O(√x) 的时间内求出 φ(x)。
2. 欧拉函数的一些性质
定理 1:∀ n>1,1∼n 之间与 n 互质的数的和为:n×φ(n)2。
证明:因为 gcd(n,x)=gcd(n,n−x),故与 n 不互质的数是成对出现的,且平均值为 n2,又考虑到共有 φ(n) 个与其互质的数,故 ∀ n>1,1∼n 之间与 n 互质的数的和为 n×φ(n)2。
定理 2:当 x 为奇数时,φ(2×x)=φ(x)。
证明 2:把 x 唯一分解:
(其中 pi≠2),2×x=2×m∏i=1pcii,根据上面的欧拉函数的求法可得:
所以当 x 为奇数时,φ(2×x)=φ(x)。
定理 3:若 x>2 ,那么 φ(x) 是偶数。
证明 3:我们考虑把 x 唯一分解:
显然,除开 2 这个质数以外,其他质数都是奇数,减去 1 之后就都是偶数了,故不包含 2 这个质因子的数的欧拉函数一定是偶数,而 2−1=1,对结果没有影响,并且如果这个数有 2 这个质因子,那么它一定是偶数,所以若 x>2 ,那么 φ(x) 是偶数。
定理 4:设 p 为质数,若 p|n 且 p2|n 则 φ(n)=φ(np)×p。
证明 4:由于 p2|n 所以 np 也有 p 这个因子,故 φ(n) 和 φ(np) 的区别只有 n 和 np 这个乘积的差别,故可得 φ(n)=φ(np)×p。
定理 5:设 p 为质数,若 p|n 且 p2∤n 则 φ(n)=φ(np)×(p−1)。
证明 5:由于 p2∤n 所以 np 与 p 互质,故 φ(n) 和 φ(np) 的比,多了 p−1p 和 p 这两个乘积,故可得:φ(n)=φ(np)×(p−1)。
定理 6:
证明:设 f(n)=∑d|nφ(d), 根据乘法分配律以及 φ 的积性性质,我们可以得到:若 n,m 互质,则
故 f(n) 是积性函数。对于单个质因子 :
我们发现这是一个等比数列求和再加 1,结果为 pci。所以
拉格朗日插值
多项式定义
n 次多项式定义为:
问题引入 1
给出 n+1 个点 (xi,yi),表示 f(xi)=yi,要求求出 f(t)。
做法 1:
高斯消元。
我们把这 n+1 个点都写出来:
把 an,an−1,⋯,a0 看作未知量,把 x,y 看作已知量,这样就可以高斯消元进行求解,解出 an,an−1,⋯,a0。
时间复杂度:O(n3)。
做法 2:
拉格朗日插值。
我们从二次多次项入手,我们给出 3 个点,(x1,y1),(x2,y2),(x3,y3)。
可以列出方程组:
这里我们换一种思路,我们构造三个函数,然后相加得到我们要的函数。
构造函数:
那么当 f(x)=y1f1(x)+y2f2(x)+y3f3(x),此时就满足 x 的取值为 x1,x2,x3 时,f(x) 的取值就为 y1,y2,y3。
现在考虑 n 次多项式,给出 n+1 个点:(x1,y1),(x2,y2),⋯,(xn+1,yn+1)。
我们考虑构造 f1∼fn+1。
设
则 fi(k) 满足在 k=xi 时函数值为 1,在 k=xj(j≠i) 时函数值为 0。
那么多项式
就满足当 k 为 x1,x2,⋯,xn+1 时 f(k) 的取值就为 y1,y2,⋯,yn+1。
如果我们只要求解特定函数值,我们可以使用逆元来代替除法。
时间复杂度:O(n2)。
优化 1
当 xa 与 a 有关系时,例如 xa=a 时,可以使用前缀乘来优化,时间复杂度:O(n)。
优化 2
重心拉格朗日插值法
当我们要解决两个操作:
- 往点集里加一个点 (x,y)
- 给出一个 k,求出当前点集对应的多项式 f(k) 的值。
考虑把原式子拿去转换。
记
则
再记
则
新加点时 O(nlogn) 求出 t(i),故每加一个点就能做到 O(nlogn) 解决。
一些常识
- n∑i=1ik 是一个 k+1 次的多项式,可以插值。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧