数论是算法竞赛和编程面试中的常客,掌握核心数论问题的解法,能显著提升你的解题效率。本文精选了洛谷平台上四道经典数论题目,覆盖素数筛法、最大公约数、最小公倍数以及质因数分解等核心知识点。我们将从问题分析、数学推导到代码实现(涉及 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') 的个数。

实现步骤:

  1. 若 y0 不能被 x0 整除,直接输出 0。
  2. 计算 t = y0 / x0,枚举 t 的所有因子 i,若 i 和 t/i 互质,则计数加 1。
  3. 注意 (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 的所有约数,检查是否满足上述两个条件即可。

优化技巧:

  1. 先对 b1 进行质因数分解,利用质因子组合生成所有约数。
  2. 对于每组数据,枚举约数个数最多约 10^5 量级(b1 ≤ 2×10^9),配合剪枝可过。
  3. 使用 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 中对应质因子的指数。

解题步骤:

  1. 对 m1 进行质因数分解,得到每个质因子 p 及其在 M 中的指数 = m2 * 原指数。
  2. 对于每个 Si,检查其是否包含 M 的所有质因子。若缺少某个质因子,则该细胞永远无法满足条件。
  3. 若包含,则计算所需的最小 t = max( ceil(所需指数 / Si中该质因子的指数) )。
  4. 取所有可行细胞中的最小 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 的数论专题,结合本文的解题思路进行实战练习。

希望本文能帮你攻克数论难关,在竞赛和面试中游刃有余!