数论是算法竞赛和编程面试中的常客,掌握核心数论问题的解法,能显著提升你的解题效率。本文精选了洛谷平台上四道经典数论题目,覆盖素数筛法、最大公约数、最小公倍数以及质因数分解等核心知识点。我们将从问题分析、数学推导到代码实现(涉及 C++、Python、JavaScript 等语言思路)进行深度拆解,帮助你建立系统的数论解题框架。
素数密度:高效区间筛法
题目背景: 给定区间 [L, R] (1 ≤ L ≤ R < 2^31, R-L ≤ 10^6),统计区间内素数的个数。这是典型的区间筛法问题,若直接使用普通筛法会因数据范围过大而超时。
解题思路: 由于区间长度 ≤ 10^6,我们可以利用埃氏筛法的思想,先筛出小于等于 √R 的所有素数(约 46340 以内的素数),然后用这些素数去标记区间 [L, R] 内的合数。这样,未被标记的数即为素数。时间复杂度约为 O((R-L) log log √R),非常高效。
✅ 实现要点:
- 注意 L 可能为 1,需要特殊处理。
- 使用布尔数组标记区间内的合数,避免使用大数组。
- 在 C++ 中可用 vector
节省内存;在 Python 中可用 bytearray。
以下为参考实现代码(C++ 风格):
#include
using namespace std;
#define re register
#define ll long long
const int maxn=1e6+1;
ll l,r;
int prime[maxn],cnt,ans;
bool vis[maxn];
inline void Gprime()
{
for(re int i=2;i<=50000;++i)
{
if(!vis[i])prime[++cnt]=i;
for(re int j=1;i*prime[j]<=50000;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0) break;
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>l>>r;
l=l==1?2:l;
Gprime();
memset(vis,0,sizeof(vis));
for(re int i=1;i<=cnt;++i)
{
ll p=prime[i],start=(l+p-1)/p*p>2*p?(l+p-1)/p*p:2*p;
for(re ll int j=start;j<=r;j+=p)vis[j-l+1]=1;
}
for(re int i=1;i<=r-l+1;++i)if(!vis[i])ans++;
cout<
⚠️ 注意事项: 2024 年 8 月新增了 Hack 数据,主要针对边界情况(如 L=1 或 R 极大时)。请确保你的代码能正确处理这些边界。
最大公约数与最小公倍数:经典推导
题目: 给定 x0, y0,求满足 P 和 Q 的最大公约数为 x0,最小公倍数为 y0 的正整数对 (P, Q) 的个数。数据范围:2 ≤ x0, y0 ≤ 10^5。
核心公式: 对于任意正整数 a, b,有 a × b = gcd(a,b) × lcm(a,b)。因此,设 P = x0 × p', Q = x0 × q',且 gcd(p', q') = 1,则 lcm(P, Q) = x0 × p' × q' = y0。从而推出 p' × q' = y0 / x0。问题转化为:求满足 p' × q' = y0/x0 且 gcd(p', q') = 1 的正整数对 (p', q') 的个数。
✅ 实现步骤:
- 若 y0 不能被 x0 整除,直接输出 0。
- 计算 t = y0 / x0,枚举 t 的所有因子 i,若 i 和 t/i 互质,则计数加 1。
- 注意 (P, Q) 与 (Q, P) 视为不同(除非相等),所以最终计数即为答案。
示例代码(Python 风格):
#include
using namespace std;
long long m,n,ans;
int main(){
cin>>m>>n;
if(m==n) ans--;
n*=m;
for(long long i=1;i<=sqrt(n);i++){
if(n%i==0&&__gcd(i,n/i)==m) ans+=2;
}
cout<
延伸思考: 此题的思路同样适用于 JavaScript 或 Go 语言,只需注意大整数运算即可。在 TypeScript 中可利用类型系统确保变量为整数。
Hankson 的趣味题:因数分解与枚举优化
题目: 已知 a0, a1, b0, b1,求满足 gcd(x, a0) = a1 且 lcm(x, b0) = b1 的正整数 x 的个数。数据范围:a0, a1, b0, b1 ≤ 2×10^9,n ≤ 2000。
数学推导:
- 由 gcd(x, a0) = a1 可知,x 是 a1 的倍数,且 x 与 a0 的公共因子仅限于 a1 的因子。
- 由 lcm(x, b0) = b1 可知,x 能整除 b1,且 b0 的因子完全包含在 b1 中。
- 因此,x 一定是 b1 的约数。我们只需枚举 b1 的所有约数,检查是否满足上述两个条件即可。
✅ 优化技巧:
- 先对 b1 进行质因数分解,利用质因子组合生成所有约数。
- 对于每组数据,枚举约数个数最多约 10^5 量级(b1 ≤ 2×10^9),配合剪枝可过。
- 使用 C++ 的快速幂或 Python 的 math.gcd 加速判断。
参考代码(C++ 实现):
#include
using namespace std;
int gcd(int a,int b) {
return b==0?a:gcd(b,a%b);
}
int main() {
int T;
scanf("%d",&T);
while(T--) {
int a0,a1,b0,b1;
scanf("%d%d%d%d",&a0,&a1,&b0,&b1);
int p=a0/a1,q=b1/b0,ans=0;
for(int x=1;x*x<=b1;x++)
if(b1%x==0){
if(x%a1==0&&gcd(x/a1,p)==1&&gcd(q,b1/x)==1) ans++;
int y=b1/x;
if(x==y) continue;
if(y%a1==0&&gcd(y/a1,p)==1&&gcd(q,b1/y)==1) ans++;
}
printf("%d\n",ans);
}
return 0;
}
⚠️ 常见陷阱: 注意 a0 能被 a1 整除,b1 能被 b0 整除,这是题目保证的条件,但解题时仍需验证 x 的合法性。
细胞分裂:质因数分解的实际应用
题目: 有 N 种细胞,第 i 种每秒分裂为 Si 个。试管总数 M = m1^m2。求最早经过多少秒,细胞总数能整除 M。若不能,输出 -1。
核心思想: 细胞分裂数 Si 的 t 次方 (Si^t) 必须能被 M 整除。问题转化为:对于每个 Si,求最小的 t 使得 Si^t 包含 M 的所有质因子且指数不小于 M 中对应质因子的指数。
✅ 解题步骤:
- 对 m1 进行质因数分解,得到每个质因子 p 及其在 M 中的指数 = m2 * 原指数。
- 对于每个 Si,检查其是否包含 M 的所有质因子。若缺少某个质因子,则该细胞永远无法满足条件。
- 若包含,则计算所需的最小 t = max( ceil(所需指数 / Si中该质因子的指数) )。
- 取所有可行细胞中的最小 t,若没有可行细胞则输出 -1。
代码示例(Python 实现):
#include
#include
#include
#include
using namespace std;
int read()
{
char ch=getchar();
int a=0,x=1;
while(ch<'0'||ch>'9')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
a=(a<<3)+(a<<1)+(ch-'0');
ch=getchar();
}
return a*x;
}
int n,m1,m2,minx,gcdd,m,s,q,t,flag,tot,ans,tots,totq;
int S[10001];
int gcd(int a,int b)
{
if(b==0) return a;
else return gcd(b,a%b);
}
int main()
{
n=read();
m1=read();
m2=read();
minx=1e9;
if(m1==1)
{
cout<<0;
return 0;
}
for(int i=1;i<=n;i++) S[i]=read();
for(int i=1;i<=n;i++)
{
tot=0;
m=m1;
s=S[i];
flag=1;
t=0;
while(m!=1)
{
gcdd=gcd(m,s);
if(gcdd==1) {flag=0;break;}
m/=gcdd;
q=s/gcdd;
s=gcdd;
t++;
}
if(flag)
{
int gc=gcd(q,s);
if(gc!=1&&gc!=s)
{
totq=0;tots=0;
while(q%gc==0)
{
totq++;
q/=gc;
}
while(s%gc==0)
{
tots++;
s/=gc;
}
if((t*m2*tots+totq*(t-1)*m2)%(tots+totq)==0)
ans=(t*m2*tots+totq*(t-1)*m2)/(tots+totq);
else ans=(t*m2*tots+totq*(t-1)*m2)/(tots+totq)+1;
minx=min(minx,ans);
}
else
{
while(q%s==0)
{
tot++;
q/=s;
}
if((t*m2+tot*(t-1)*m2)%(tot+1)==0)
ans=(t*m2+tot*(t-1)*m2)/(tot+1);
else ans=(t*m2+tot*(t-1)*m2)/(tot+1)+1;
minx=min(minx,ans);
}
}
}
if(minx==1e9) cout<<-1;
else cout<
技术延伸: 此题的思路在密码学中也有应用,例如在 RSA 算法中需要分解大合数。掌握质因数分解的优化方法(如 Pollard's Rho)能让你在更大数据范围下游刃有余。
[AFFILIATE_SLOT_1] 如果你对数论算法感兴趣,推荐阅读《算法竞赛入门经典》中的数论章节,系统学习筛法、欧拉函数等进阶内容。
总结与建议
本文通过四道经典题目,系统梳理了数论中最重要的几个概念:素数筛法、最大公约数与最小公倍数、质因数分解。这些知识点在 C++、Python、JavaScript、Go 等主流语言中都有成熟的标准库支持(如 math.gcd、BigInteger 等),但理解其原理能帮助你写出更高效的代码。
实践建议:
- 在刷题时,先尝试数学推导,再动手编码。
- 注意数据范围,选择合适的算法(如区间筛法代替普通筛法)。
- 多语言对比学习:用 C++ 追求极致性能,用 Python 快速验证思路,用 JavaScript/TypeScript 理解动态类型下的数论实现。
[AFFILIATE_SLOT_2] 想要提升算法能力?不妨试试 LeetCode 的数论专题,结合本文的解题思路进行实战练习。
希望本文能帮你攻克数论难关,在竞赛和面试中游刃有余!
浙公网安备 33010602011771号