约数之和
题目链接:
https://www.acwing.com/problem/content/99/
题目简介:
假设现在有两个自然数A和B,S是ABAB的所有约数之和。
请你求出S mod 9901的值是多少。
输入格式
在一行中输入用空格隔开的两个整数A和B。
输出格式
输出一个整数,代表S mod 9901的值。
数据范围
0≤A,B≤5×1070≤A,B≤5×107
输入样例:
2 3
输出样例:
15
注意: A和B不会同时为0。
数学知识分析:
名词解释:
**约数(因数):**可以被其整除的数就称之为约数或者因数
**质因数:**就是是质数的因数
**约数之和:**就是将一个数的所有约数进行求和
分解质因数:
分解质因数的操作:
一个数除以最小的质数(2),如果可以整除就继续用这个数一直除,如果不能整除换第二小质数(3),从小到大,会发现最终会被几个质数的几次方所整除,而这几个质数就是这个数的质因数。
例如:求72的质因数
求约公式:
其中pi是两两互质的正整数也就是上面提到的质因数
约数之和的公式:
s u m = ( p 1 0 + p 1 1 + p 1 2 + . . . + p 1 a 1 ) ∗ ( p 2 0 + p 2 1 + p 2 2 + . . . + p 2 a 2 ) ∗ . . ∗ ( p k 0 + p k 1 + p k 2 + . . . + p k a k ) sum = (p_1^0+p_1^1+p_1^2+...+p_1^{a_1})*(p_2^0+p_2^1+p_2^2+...+p_2^{a_2})*..*(p_k^0+p_k^1+p_k^2+...+p_k^{a_k}) sum=(p10+p11+p12+...+p1a1)∗(p20+p21+p22+...+p2a2)∗..∗(pk0+pk1+pk2+...+pkak)
题目分析:
要根据求约公式求出数 A B A^B AB的质因数,并以此利用约数之和公式求出答案。
有两个难点:
- 怎么求质因数
- 怎么实现约数之和公式
代码实现求质因数
//求n的质因数
for(int i = 2; i < n; i++){
//用于技术被i除了几次,就相当于当前质数的几次方
int s = 0;
while(n%i == 0){
//每除一次就++
s++;
n/=i;
}
//输出质因数的几次方
cout<<i<<"^"<<s<<" * ";
}
一开始会有点疑惑为什么for循环每层都只加1,而求质因数不是每次要求除的数都是质数吗?
仔细思考一下:
所有不是质数的数都可以由比他小的质数相乘得到。所以当无法被比他小的质数整除的时候当然也无法被其整除,所以会跳过,只有当i为质数的时候才会进入while循环。
例如:
如上所示,每一个合数都会被比他小的质数组合相乘而得,有的质数可能会利用多次(8=2*2*2),但是因为我们这边是使用while循环将每一个数都整除到不能整除,所以每一个数都除到它的最大值。
代码实现约数之和公式:
可以发现约数之和的公式是多个等比数列之和的成绩。所以只要能够实现等比数列之和就可以实现约数之和公式。
分治:$p0+p1+p2+…+pk $
= ( p 0 + p 1 + . . . + p k / 2 ) + ( p k / 2 + 1 + p k / 2 + 2 + . . . + p k ) = (p^0+p^1+...+p^{k/2})+(p^{k/2+1}+p^{k/2+2}+...+p^{k}) =(p0+p1+...+pk/2)+(pk/2+1+pk/2+2+...+pk)
= ( p 0 + p 1 + . . . + p k / 2 ) + p k / 2 + 1 ∗ ( p 0 + p 1 + . . . + p k / 2 ) = (p^0+p^1+...+p^{k/2})+p^{k/2+1} *(p^0+p^1+...+p^{k/2}) =(p0+p1+...+pk/2)+pk/2+1∗(p0+p1+...+pk/2)
= ( p 0 + p 1 + . . . + p k / 2 ) ∗ ( 1 + p k / 2 + 1 ) =(p^0+p^1+...+p^{k/2})*(1+p^{k/2+1}) =(p0+p1+...+pk/2)∗(1+pk/2+1)
通过二分治的方法将原本的问题规模缩小为原来的一一半!
就是因为是有规律的所以当求偶数个数的时候可以将其拆分成两半进行计算!这样计算的规模就小了一半
int sum(int p,int k){
//当求0次方的时候就返回1
if(k == 0) return 1;
//当所求的次幂是“奇数个”时,注意我这边指的是个数
if(k % 2 == 0) return p*sum(p,k-1)+1;
//ksm()是快速幂函数
//当所求的次幂个数为偶数个时
return sum(p,k/2)*(1+ksm(p,k/2+1));
}
奇数个时:
就是相当于少算一个,但是为了结果不变要乘以p再加上1.进行下层递归的时候由于原本的奇数,少算一个(k-1)之后就会变成偶数,就又进入了分治的算法范围。
偶数个时
分治应该要偶数个才能进行,这样刚好分成两份,但是代码却是次幂数(k % 2 !=0
)为奇数的时候进行,这是因为我们所有的等比数列是从0开始的,所以要在次幂数的基础上进行加1,所以次幂数为奇数的时候个数反而是偶数。
这样又会引发一个问题:上面分治的时候是用次幂数k/2进行,如果k为奇数这样将不能整除。
这里就很巧妙,因为c++里面如果两个整型进行运算得出的结果有小数的话会选择向下取整,也就是会舍弃小数的部分,而k/2向下取整刚好是前半部分的最后一个,而k/2+1刚好是前半部分的第一个,这就非常的巧妙!如下图所示
为了验证这个结果,我们将代码修改一下:
int sum(int p,int k){
if(k == 0) return 1;
if(k % 2 == 0) return p*sum(p,k-1)+1;
return sum(p,(k-1)/2)*(1+ksm(p,(k-1)/2+1));
}
将原本的k/2 改为 k-1 /2
如果推论没有错误的话结果应该是一样的
最后结果也是通过了,当然这只是我做题上面上的一些疑惑,想通之后就发现很巧妙!
全部代码:
#include<iostream>
using namespace std;
const int mod = 9901;
//快速幂
int ksm(int p , int k){
p %=mod;
int res = 1;
while (k)
{
if (k & 1) res = res * p%mod ;
p = p * p%mod;
k >>= 1;
}
return res;
}
//求等比数列的和
int sum(int p,int k){
if(k == 0)return 1;
if(k%2==0) return (p%mod*sum(p,k-1)%mod+1)%mod;
return sum(p,(k-1)/2)%mod*(1+ksm(p,(k-1)/2+1))%mod;
}
int main(){
int A,B;
cin>>A>>B;
int res = 1;
//求解质因数
for(int i = 2; i <= A; i++){
int k = 0;
while(A%i == 0){
k++;
A/=i;
}
//求约数之和
if(k) res=res*sum(i,k*B)%mod;
}
if(!A) res = 0;
cout<<res<<endl;
return 0;
}
int res = 1;
//求解质因数
for(int i = 2; i <= A; i++){
int k = 0;
while(A%i == 0){
k++;
A/=i;
}
//求约数之和
if(k) res=res*sum(i,k*B)%mod;
}
if(!A) res = 0;
cout<<res<<endl;
return 0;
}
> 这些代码都不是自己写的,都是看理解大佬的代码跟着他们的思路自己打一遍的,基本上都是原原本本的。自己只是将思路再理一遍加深一下理解