POJ - 1845
Consider two natural numbers A and B. Let S be the sum of all natural divisors of A^B. Determine S modulo 9901 (the rest of the division of S by 9901).
Input
The only line contains the two natural numbers A and B, (0 <= A,B <= 50000000)separated by blanks.
Output
The only line of the output will contain S modulo 9901.
Sample Input
2 3
Sample Output
15
Hint
2^3 = 8.
The natural divisors of 8 are: 1,2,4,8. Their sum is 15.
15 modulo 9901 is 15 (that should be output).
这里用到数论的知识只要一下三条性质
(1) 整数的唯一分解定理:
任意正整数都有且只有一种方式写出其素因子的乘积表达式。
A=(p1^k1)*(p2^k2)*(p3^k3)*....*(pn^kn) 其中pi均为素数
(2) 约数和公式:
对于已经分解的整数A=(p1^k1)*(p2^k2)*(p3^k3)*....*(pn^kn)
有A的所有因子之和为
S = (1+p1+p1^2+p1^3+...p1^k1) * (1+p2+p2^2+p2^3+….p2^k2) * (1+p3+ p3^3+…+ p3^k3) * .... * (1+pn+pn^2+pn^3+...pn^kn)
(3) 同余模公式:
(a+b)%m=(a%m+b%m)%m
(a*b)%m=(a%m*b%m)%m
三条性质环环相扣,首先要求出所有的素因子,然后通过素因子用求和公式求出所有因子和,两种方法的不同就在于用约束和公式时,求等比数列的方式不同
方法一、:等比数列公式求和
这种方法很容易想到,但是却有很多的坑
首先第一个坑就是等比数列求和公式的分类讨论
①当q≠1时,
或
②当q=1时,
容易忘的就是公比为“”1“”的地方,为什么这里加引号,因为题目要求取模,也就是q有可能实际上并不是1,只是q和1取模同余(即q≡1(mod m)即(q-1)%m=0,这是同余定义)。所以最终取完模后还是按1算
其次当q≠1的一般情况下用公式时,因为取模没有对除法的分配律,所以不能分别给分子取模分母取模,又因为我们求分子的时候用的是快速幂取模,所以就无意的把取模分配给了分子,但是又不能直接分配给分母,所以要求出分母的逆元,然后变成乘法,就可以分配了,(乘法可以分配),求逆元的方法有两种费马小定理和扩展欧几里得,这里用费马小定理求较为方便
费马小定理:a^(p-1)≡1(mod p)
那么由此逆元可以求 a^(p-2) ≡ a^(-1) (mod p)
code1:
#include
#include
#include
#define Mod 9901
using namespace std;
typedef long long ll;
const int MAXN = 1e5+10;
ll q_pow(ll a,ll b){
ll ans = 1;
a = a%Mod;
while(b){
if(b&1){
ans = ans*a%Mod;
}
a = a*a%Mod;
b >>= 1;
}
return ans%Mod;
}//快速幂
ll getreverse(ll x){
return q_pow(x,(ll)(Mod-2))%Mod;
}//费马小定理求逆元
ll cal(ll p,ll cnt,ll b){
ll tmp1 = (q_pow(p,cnt*b+(ll)1)-(ll)1)%Mod;
ll reversex = getreverse(p-(ll)1)%Mod;
return tmp1*reversex%Mod;
}//等比数列求和
int main(){
ll a,b;
ll p[MAXN];
ll k[MAXN];
scanf("%lld%lld",&a,&b);
ll cnt = 0;
ll i;
ll ans = 1;
for(i = 2; i*i <= a; i++){
if(a%i==0){
p[cnt] = i;
k[cnt] = 0;
while(a%i==0){
a /= i;
k[cnt]++;
}
cnt++;
}
}
if(a!=1){
p[cnt] = a;
k[cnt++] = 1;
}
//注意使用公式法求时也要分公比为“1”和公比不为1的情况
for(i = 0; i < cnt; i++){
if((p[i]-1)%Mod==0)//注意注意这里不能写p[i] = 1,因为要取模,所以如果p[i]恰好和1取模Mod同余,即使p[i]不是1最终
//算的时候还是按取完模的1计算。
ans = ans*(1+b*k[i])%Mod;
else
ans = ans*cal(p[i],k[i],b)%Mod;//一般情况用公式的时候要注意分母p-1要用费马小定理求出其逆元,因为取模对除法没有分配律
}
while(ans<0)
ans += Mod;
printf("%lld\n",ans);
return 0;
}
方法二、用等比数列递归求和
这种方法就直接解决的分类讨论和取模需要求逆元的困扰,直接递归相乘取模即可
(1)若n为奇数,一共有偶数项,则:
1 + p + p^2 + p^3 +...+ p^n
= (1+p^(n/2+1)) + p * (1+p^(n/2+1)) +...+ p^(n/2) * (1+p^(n/2+1))
= (1 + p + p^2 +...+ p^(n/2)) * (1 + p^(n/2+1))
上式红色加粗的前半部分恰好就是原式的一半,那么只需要不断递归二分求和就可以了,后半部分为幂次式,将在下面第4点讲述计算方法。
(2)若n为偶数,一共有奇数项,则:
1 + p + p^2 + p^3 +...+ p^n
= (1+p^(n/2+1)) + p * (1+p^(n/2+1)) +...+ p^(n/2-1) * (1+p^(n/2+1)) + p^(n/2)
= (1 + p + p^2 +...+ p^(n/2-1)) * (1+p^(n/2+1)) + p^(n/2);
上式红色加粗的前半部分恰好就是原式的一半,依然递归求解
code2:
#include
#include
#include
#define Mod 9901
using namespace std;
typedef long long ll;
const int MAXN = 1e5+10;
ll q_pow(ll a,ll b){
ll ans = 1;
a = a%Mod;
while(b){
if(b&1){
ans = ans*a%Mod;
}
a = a*a%Mod;
b >>= 1;
}
return ans%Mod;
}//快速幂
ll sum(ll p, ll n){//递归求等比数列和
if(n == 0)
return 1;
if(n&1)
return ((1+q_pow(p,n/2+1))%Mod*sum(p,n/2)%Mod)%Mod;
else
return ((1+q_pow(p,n/2+1))%Mod*sum(p,(n-1)/2)%Mod+q_pow(p,n/2)%Mod)%Mod;
}
int main(){
ll a,b;
ll p[MAXN];
ll k[MAXN];
scanf("%lld%lld",&a,&b);
ll cnt = 0;
ll i;
ll ans = 1;
for(i = 2; i*i <= a; i++){
if(a%i==0){
p[cnt] = i;
k[cnt] = 0;
while(a%i==0){
a /= i;
k[cnt]++;
}
cnt++;
}
}
if(a!=1){
p[cnt] = a;
k[cnt++] = 1;
}
for(i = 0; i < cnt; i++){
ans = ans*sum(p[i],b*k[i])%Mod;
}
printf("%lld\n",ans);
return 0;
}