递推与递归4 约数之和

本文介绍了一种利用分治策略计算数A质因数分解后约数之和的方法,通过将问题规模从O(k)降低到O(logk),简化了计算过程。关键步骤包括定义sum(p,k)函数并递归应用。代码实例展示了如何用C++实现这一高效算法。

在这里插入图片描述
对于一个数x,假设他分解质因数后的形式为p1a1 *p2a2 *p3a3 。则他的约数之和为(1+p1+···+p1a1)*(1+p2+···+p2a2)*(1+p3+···+p3a3)。
故本题只需要将A分解质因数,每个质因子的数量乘以B。再按照上式计算即可。
如何计算呢,我们可以依次计算每个质因子,最后相乘。但是由于A分解质因数后需要乘以B,可能会使次数很大,所以我们无法一次一次的循环。
考虑分治,对于每一个质因子,令sum(p,k)=(1+p+···+pk):
如果k是偶数,则sum(p,k)=(1+p+···+pk/2-1)+pk/2+(pk/2+1+···pk)=(1+p+···+pk/2-1)+pk/2+pk/2+1 *(1+p+···+pk/2-1)=sum(p,k/2-1)(1+pk/2+1)+pk/2
如果k是奇数,类似的sum(p,k)=sum(p,k/2)*(1+k/2+1)。
这样就将O(k)优化到了O(logk)轻松通过。
代码:

#include<bits/stdc++.h>
using namespace std;
const int mod = 9901;
typedef pair<int, int> PII;
int a,b;
int qmi(int a,int b,int p){
    a%=p;
    int res=1;
    while(b){
        if(b&1)res=res*a%p;
        b>>=1;
        a=a*a%p;
    }
    return res;
}
int sum(int p,int k){
    if(k==0)return 1;
    if(k&1)return sum(p,k/2)*(1+qmi(p,k/2+1,mod))%mod;
    return ((qmi(p,k/2+1,mod)+1)*sum(p,k/2-1)%mod+qmi(p,k/2,mod))%mod;
}
int main(){
    cin>>a>>b;
    if(a==0){
        cout<<0;
        return 0;
    }
    vector<PII>primes;
    for(int i=2;i<=a&&a>1;i++){
        if(a%i==0){
            int cnt=0;
            while(a%i==0){
                cnt++;
                a/=i;
            }
            if(b)primes.push_back({i,cnt*b});
        }  
    }
    if(a>1&&b)primes.push_back({a,b});
    int res=1;
    for(auto [p,k]:primes){
        res=res*sum(p,k)%mod;
    }
    cout<<res;
    return 0;
}
<think> 我们已知用户要使用递归最大公约数,根据引用[2][3][4]中提供的递归方法,我们可以实现如下: 递归的核心思想是:gcd(a, b) = gcd(b, a % b),直到余数为0,此时除数即为最大公约数。 具体步骤: 1. 如果b等于0,那么a就是最大公约数递归的终止条件)。 2. 否则,计算a除以b的余数,然后用b和余数作为新的参数进行递归。 注意:我们不需要保证a>=b,因为如果a<b,那么a%b等于a,所以递归调用变为gcd(b, a),这样自然就转换成了a>=b的情况。 因此,递归函数可以这样写: if (b == 0) return a; else return gcd(b, a % b); 但是,在引用[3]和[4]中,终止条件是当a%b==0时返回b。这里注意,当b为0时,取模操作可能会引发除零错误。因此,我们需要避免b为0的情况。 实际上,在递归过程中,由于每次调用时第二个参数是上一次的余数,而余数会越来越小,最终会变成0。当b为0时,根据数学定义,最大公约数就是a。所以更通用的写法是: int gcd(int a, int b) { if (b == 0) return a; return gcd(b, a % b); } 但是,引用[3][4]中的写法是判断a%b==0,这实际上是在判断余数为0,此时除数b就是最大公约数。这两种写法本质上是等价的,因为当余数为0时,下一次递归调用就会遇到b=0的情况。不过,引用[3][4]的写法实际上少了一次递归调用,因为它直接返回了当前的除数b,而避免了再调用一次gcd(b,0)。所以我们可以写成: if (a % b == 0) return b; else return gcd(b, a % b); 但是,这种写法要初始调用时b不能为0,并且如果b为0,那么a%b会出错。因此,我们可以在函数开始时检查b是否为0,或者保证调用时b不为0。另外,当a小于b时,a%b就是a,所以递归调用会变成gcd(b, a),这样也能正确计算。 为了安全,我们可以先判断b是否为0,如果为0,则返回a(因为gcd(a,0)=a)。或者,我们也可以按照第一种写法,将终止条件设为b==0。 这里我们采用更通用的第一种写法(能处理b为0的情况),同时注意避免除零错误。 因此,递归函数的实现如下: ```c #include <stdio.h> int gcd(int a, int b) { if (b == 0) { return a; } return gcd(b, a % b); } int main() { int num1, num2; printf("输入两个整数: "); scanf("%d %d", &num1, &num2); // 处理负数:最大公约数通常定义为正整数,所以取绝对值 // 但注意,取模运算在负数时依赖于编译器,所以最好用非负数 // 我们可以先转换为非负 int result = gcd(abs(num1), abs(num2)); printf("最大公约数是: %d\n", result); return 0; } ``` 注意:由于负数取模在C语言中依赖于实现,为了安全,我们在计算前取绝对值。 另外,引用[3]中给出了一个例子,但它的代码中直接调用了fun(40,36),这个例子中40和36的最大公约数4。 我们也可以采用引用[3][4]的写法,但需要确保b不为0。我们可以在函数内部先判断b是否为0,如果是0,则返回a;否则,再用a%b。但这样写就会变成两个条件。因此,我们采用上述的简洁写法。 另外,引用[2]也描述了同样的递归过程。 所以,我们提供上述递归实现。 下面我们解释一下递归过程: 例如:gcd(40,36) 第一步:40 % 36 = 4递归调用gcd(36,4) 第二步:36 % 4 = 0,所以返回4 因此,最终结果为4。 再例如:gcd(36,40) 第一步:36 % 40 = 36,递归调用gcd(40,36) -> 然后同上,得到4。 因此,这个递归函数能够自动处理a和b的大小关系。 时间复杂度:递推法相同,$O(\log(\min(a, b)))$。 但是,递归调用会有栈空间的开销,深度为递归的层数,所以当数字非常大时,递归深度可能很大,有栈溢出的风险。 因此,对于大整数,通常使用递推(迭代)法更安全。 现在,我们根据用户要,提供递归法的实现。 同时,我们也要注意,如果输入两个0,那么最大公约数应该是0?但是数学上gcd(0,0)通常是没有定义的,但我们的代码会返回0(因为b=0,返回a,而a=0)。所以,我们可以在输入后判断两个数是否都为0,然后特殊处理。 修改一下,处理两个0的情况: ```c #include <stdio.h> #include <stdlib.h> // 为了使用abs int gcd(int a, int b) { if (b == 0) { return a; // 如果a也是0,那么返回0 } return gcd(b, a % b); } int main() { int num1, num2; printf("输入两个整数: "); scanf("%d %d", &num1, &num2); if (num1 == 0 && num2 == 0) { printf("两个数都是0,没有最大公约数\n"); return 0; } int result = gcd(abs(num1), abs(num2)); printf("最大公约数是: %d\n", result); return 0; } ``` 这样,当输入两个0时,我们给出提示。 但是,递归函数在遇到两个0时,会返回0,所以实际上也可以不特殊处理,但数学上0和0的最大公约数通常未定义,所以特殊处理一下更好。 综上所述,我们提供了递归最大公约数的C语言实现。 最后,我们生成相关问题。 </think> ### C语言使用递归最大公约数 在C语言中,使用递归最大公约数的核心是**欧几里得算法**(辗转相除法)。其数学原理为: $$ \gcd(a, b) = \gcd(b, a \bmod b) \quad (b \neq 0) $$ $$ \gcd(a, 0) = a $$ #### 递归实现代码 ```c #include <stdio.h> #include <stdlib.h> // 用于abs()函数处理负数 // 递归函数最大公约数 int gcd(int a, int b) { // 确保b不为负数 b = abs(b); // 递归终止条件:当余数为0时,b即为最大公约数 if (a % b == 0) { return b; } // 递归调用:用b和a%b作为新参数 return gcd(b, a % b); } int main() { int num1, num2; printf("输入两个整数: "); scanf("%d %d", &num1, &num2); // 处理特殊情况:两数都为0 if (num1 == 0 && num2 == 0) { printf("0和0没有最大公约数\n"); return 0; } // 调用递归函数(取绝对值确保正数) int result = gcd(abs(num1), abs(num2)); printf("最大公约数是: %d\n", result); return 0; } ``` #### 代码解析 1. **递归函数设计**: - **终止条件**:`if (a % b == 0)` 当余数为0时返回除数 `b` - **递归步骤**:`return gcd(b, a % b)` 用除数和余数继续递归 2. **参数处理**: - 使用 `abs()` 确保参数为正数(最大公约数定义为正整数) - 单独处理 `(0,0)` 的特殊情况(数学上未定义) 3. **递归过程示例**(gcd(48, 18)): ``` 第1次调用: gcd(48, 18) → 48 % 18 = 12 第2次调用: gcd(18, 12) → 18 % 12 = 6 第3次调用: gcd(12, 6) → 12 % 6 = 0 → 返回6 ``` #### 算法特性 | 特性 | 说明 | |--------------|-------------------------------| | 时间复杂度 | $O(\log(\min(a, b)))$ | | 空间复杂度 | $O(\log(\min(a, b)))$(递归栈)| | 优点 | 代码简洁,数学表达直观 | | 缺点 | 大整数可能栈溢出 | > ⚠️ **注意**:递归深度受系统栈大小限制,处理极大整数时建议改用迭代法[^1][^3]。 #### 递归 vs 迭代 | 方法 | 代码简洁性 | 栈空间使用 | 适用场景 | |--------|------------|------------|----------------| | 递归 | ★★★★★ | 高 | 小整数计算 | | 迭代 | ★★★☆☆ | 恒定$O(1)$ | 大整数计算 | 递归法通过不断缩小问题规模($a \to b, b \to a \bmod b$),最终在余数为0时得到解,完美体现了**分治思想**在数论中的应用[^2][^4]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值