数论基础
数论基础
余数
取模运算
改编自 deepseek
整数取模
在C++中,取模运算(%)遵循以下规则:
- 取模运算的语法:
a % b,其中 a 是被除数,b 是除数 - 操作数必须是整数类型(int, long, short, char 等)
- 结果的符号与被除数 a 相同
- 如果b为0,会导致未定义行为
cout << 7 % 3; // 输出1 (7 = 3*2 + 1)
cout << -7 % 3; // 输出-1 (-7 = 3*(-2) + (-1))
cout << 7 % -3; // 输出1 (7 = -3*(-2) + 1)
cout << -7 % -3; // 输出-1 (-7 = -3*2 + (-1))
浮点数取模
C++标准库提供了 fmod() 函数用于浮点数的取模运算:
#include <cmath>
double result = fmod(5.3, 2.0); // 结果为1.3
整除分块
参考博客 然而我认为这篇的时间复杂度分析得不太对。
求 $\sum_{i=1}^{n} \lfloor \frac{n}{i} \rfloor $ 这种东西以及其他复杂很多的东西,可以 \(O(\sqrt{n})\) 求解。
最简单地,求 $\sum_{i=1}^{n} \lfloor \frac{n}{i} \rfloor $。
时间复杂度证明
注意到 \(\lfloor \frac{n}{i} \rfloor\) 的取值不超过 \(2\sqrt{n}\) 种。证明如下:
- \(i\le \sqrt{n}\),因为 \(i\) 只有 \(\sqrt{n}\) 种,所以 \(\lfloor \frac{n}{i} \rfloor\) 的取值也只有 \(\sqrt{n}\) 种。
- \(i> \sqrt{n}\),此时 \(\lfloor \frac{n}{i} \rfloor < \sqrt{n}\),显然取值不超过 \(\sqrt{n}\)。
因此总时间复杂度为 \(O(\sqrt{n})\)。
大体思路和代码
枚举 \(l\),设 \(v=\lfloor \frac{n}{l} \rfloor\),我们要找 \(v=\lfloor \frac{n}{r} \rfloor\) 的最大的 \(r\)。
因此在这个例子中,\(r\) 取 \(\frac{n}{v}\),下一块的 \(l\) 就取 \(r+1\)。
for(int l=1,r=1,v=n/l;l<=n;l=r+1,v=n/l){
r=n/v;
ans+=v*(r-l+1);
}
经验
P1403 [AHOI2005] 约数研究 一道小黄题,和例子一模一样,其实它的数据范围可以不用整除分块的。
P2424 约数和 很板子。
P2261 [CQOI2007] 余数求和 把 \(k\bmod i\) 转化成 \(k-\lfloor \frac{k}{i} \rfloor i\),然后看起来非常板子。但是由于 \(i\le n\) 而本题求 \(\lfloor \frac{k}{i} \rfloor\),\(n,k\) 可能不等,需要注意边界细节。
GZOI2024 Day2 T2【乒乓球】 很复杂的整除分块。
约数
\(a|b\) 表示 \(a\) 能整除 \(b\),即 \(a\) 是 \(b\) 的因子。
约数个数大约 \(O(n^\frac{1}{3})\) 级别。
最大公约数
——辗转相除法求 gcd
欧几里得算法: \(\gcd(a,b)=\gcd(b,a\bmod b)\) 。
时间复杂度 \(O(\log n)\)(我不会证明 qwq)。
int gcd(int a,int b) { return b ? gcd(b,a%b) : a; }
O(1) 求 gcd
参考 O(1) gcd 学习笔记。
设 \(T=\lfloor\sqrt{V}\rfloor\)。
对于一个数 \(1\le x\le V\),一定可以拆分成三个数 \(a,b,c\) 相乘,使得每个数 \(y\) 满足:
- 若 \(y > T\) 则 \(y\) 是质数。
- 否则 \(y \le T\),\(y\) 可以是合数。
因为 \(x\) 可能有至多一个质因子 \(p> T\),如果有,则把 \(x\) 分成 \(p\) 和 \(q\),\(q\) 一定是一个 \(\le T\) 的数,否则 \(pq\) 就 \(\ge (T+1)^2\) 了。那么把 \(q\) 拆成两个数也一定都是 $\le T $ 的。如果没有,假设存在一个合数 \(>T\),那么另外两个数的乘积一定 \(\le T\),否则他们三乘起来就 \(\ge (T+1)^2\) 了。这时候我们把两个小的数乘起来变成一个,然后把大的合数拆开就好了。emm,感性理解一下吧。
不是,我为什么要证明这个东西啊,学 OI 还纠结什么证明?
好的,接下来我们不再证明这些显然的东西了。
O(n) 预处理
线性筛筛出每个数的最小质因数 \(p\),然后这个数的分解方式复制 \(x/p\) 的分解方式,然后从 \(a,b,c\) 依次判断能否乘上 \(p\) 之后仍然合法,合法就乘上好了。
然后预处理出所有 \(\le T\) 的数的两两 \(\gcd\),可以类似于辗转相除递推。
每次查询两个数 \(\gcd\) 时就可以 \(O(1)\) 求了!
线性同余方程
给定 \(a,b,n\),求 \(x\) 在 \([0,n-1]\) 的整数解。
当 \(a,n\) 互素时,可以逆元求解。
若 \(a,n\) 不互素,此时不一定有解。当且仅当 \(\gcd (a,n) | b\) 的时候有解且恰有 \(\gcd(a,n)\) 个解,可以感性理解。使用逆元或者扩展欧几里得算法求解。
逆元求解
设 \(g=\gcd(a,n)\)。
方程左右及模数同除 \(g\)。
令 \(a'=\frac{a}{g},b'=\frac{b}{g},n'=\frac{n}{g}\),
有 \(a' x' \equiv b' \pmod {n'}\)。
可以发现 \(x'\) 也是原方程的一个解。
原方程有 \(g\) 个解。通解为 \(x_i = x'+in'(i \in [0,g-1])\)。
显然这样的解是新方程的解,又因为这样的解 \(\le n-1\),因此这样的解也是原方程的解。
扩展欧几里得算法 exgcd
因为我是 OIer,所以直接上代码,证明不重要。
l x,y;
ll exgcd(int a,int b) {
if(!b) {
x=1, y=0;
return a;
}
int d=exgcd(b,a%b);
int t=x;
x=y;
y=t-a/b*y;
return d;
}
void solve(int a,int b,int c) { // ax+by=c
int g=exgcd(a,b);
if(c%g) return puts("-1"), void(0); // 无整数解
x*=c/g, y*=c/g;
ll tx=b/g, ty=a/g;
ll k=ceil(1.0*(-x+1)/tx);
x+=k*tx, y-=k*ty;
if(y<=0) { // 无 x,y 均是正整数的解
ll ymn=y+ty*ceil(1.0*(-y+1)/ty);
pf("%lld %lld\n",x,ymn); // xmin,ymin
return;
}
ll ky=ceil(1.0*(-y+1)/ty);
pf("%lld %lld %lld %lld %lld\n",(y-1)/ty+1,x,y+ty*ky,x-ky*tx,y); // 正整数解的个数,xmin,ymin,xmax,ymax
}
同余方程:
可以转化为
当且仅当 \(\gcd(a,b) \mid c\) 的时候有且有 \(\gcd(a,b)\) 个解。
假设 \(gcd(a,b) \mid c\),设 \(g=gcd(a,b)\)。
方程
的解集和原方程相等。
上面的方程写作
我们先求方程 \(a' x + b' y = \gcd (a', b')\) 的解。
当 \(b'=0\) 的时候,显然有 \(\gcd=a',x=1,y=0\)。
当 \(b' > 0\) 的时候,参考辗转相除法的过程。求出方程 \(b' x' + (a' \bmod b') y' = \gcd\) 的解。有
因为 \(a',b'\) 互质,由此得出
于是我们求出了方程 \(a' x + b' y = \gcd (a', b')=1\) 的一组解。两边同乘 \(c'\),就得到了 \(a' x + b' y = c'\) 的一组特解,记作 \(x_0,y_0\)。也是原方程的一组特解。
然后求通解。
已知
加上 \(d_a\) 个 \(a\),减去 \(d_b\) 个 \(b\),使方程仍然成立 \(d_a,d_b\) 均为整数。求 \(d_a,d_b\) 需要满足什么条件。
显然 \(\frac{\text{lcm}(a,b)}{a} \mid d_a, \frac{\text{lcm}(a,b)}{b} \mid d_b\)。其他写法是一个意思。一般写作 \(\gcd\) 的形式,即 \(\frac{b}{\gcd(a,b)}\)。
有了 \(d_a\) 的最小变化单位不难求出方程最小正整数解等类似问题。
素数和合数
一个数有且仅有 \(1\) 和它本身两个因子,这个数是素数,因此 \(1\) 不是素数。
线性筛法
每个数只会被最小的质因子筛到。
复杂度 \(O(n)\) 。
Code
#include<bits/stdc++.h>
#define ll long long
#define pf printf
#define sf scanf
using namespace std;
const int N=1e6+7,M=1e7;
int can[N];
int p[M];
void primes(int n){
for(int i=2;i<=n;i++){
if(!p[i]){
p[i]=i;
can[++can[0]]=i;
}
for(int j=1;j<=can[0];j++){
if(can[j]>p[i]||(ll)can[j]*i>n) break;
p[can[j]*i]=can[j];
}
}
}
int main(){
int n=M;
primes(n);
pf("%d\n",can[0]);
}
乘法逆元
则 \(x\) 是 \(a\) 的乘法逆元。
若 \(a,p\) 不互素,则不存在乘法逆元。
线性求逆元
求连续逆元
结论、证明见 OIWiki。这里搬一张结论。

附上可爱代码:
inv[1]=1;
rep(i,2,n) inv[i]=mul(mod-mod/i,inv[mod%i]);
求任意 n 个数的逆元
设 \(s_n\) 表示前 \(n\) 个数的积(取模意义下),先求出 \(s_n^{-1}\),然后倒序一次求出 \(s_i^{-1}\),最后 \(inv_i \gets s_i^{-1} \times s_{i-1}\)。
s[0]=1;
rep(i,1,n) s[i]=1ll*s[i-1]*a[i]%p;
sinv[n]=ksm(s[n],p-2);
per(i,n-1,0) sinv[i]=1ll*sinv[i+1]*a[i+1]%p;
rep(i,1,n) inv[i]=1ll*sinv[i]*s[i-1]%p;
本文来自博客园,作者:wing_heart,转载请注明原文链接:https://www.cnblogs.com/wingheart/p/18359548

浙公网安备 33010602011771号