POJ---1845:Sumdiv【因数和定理】

该博客主要介绍了如何利用因数和定理解决POJ 1845问题,即求解A^B的所有约数和并模9901。博主分析了约数和定理,并提供了两种解法:一种是根据等比数列求和公式直接计算,另一种是递归计算1 + p + p^1 + ... + p^n。博客给出了相应的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

求 A^B 的所有约数和,输出结果 mod 9901

分析:

约数和定理:

A^B=p_1^{a_1*B}*p_2^{a_2*B}*p_3^{a_3*B}*...*p_k^{a_k*B}

A^B 的质因子无非是A的每个质因子的个数扩大了B倍,直接套公式,等比数列求和有:

\frac{p_i^n-1}{p_i-1}~mod~9901,模数太小,pi - 1可能和9901不互质,就不能通过逆元来求,下面给出两种解法:

求解:

(1)   a/b~mod~c=a~mod~(b*c)/b

上面公式仅在 b|a 时成立,我们可以反向验证这个公式

设 x = a / b,则 a = b * x,那么 a % bc = bx % bc,设 bx % bc = k,则 bx = y*bc + k,等式两边同时除以 b 得到:x = y*c + k/b,即:x % c = k/b,得证:a/b % c = a % bc / b 

(y*bc + k) % b = (bx % b) = 0,得到 k % b = 0,即a % bc / b 一定是个整数

这个公式就不涉及到逆元,而(p-1)|(p^n - 1)(容易证明),套上公式即可

代码:

#include <iostream>
#include <algorithm>

using namespace std;
typedef long long LL;
const LL mod = 9901;
LL T,a,b,p[1000],num[1000];
int cal(){
    int len = 0;
    for(int i = 2;1LL*i*i <= a; ++i){
        if(a % i == 0){
            p[len] = i;
            while(a % i == 0){
                num[len]++;
                a /= i;
            }
            len++;
        }
    }
    if(a > 1){p[len] = a;num[len++] = 1;}
    return len;
}
LL qmul(LL a,LL b,LL MOD){
    LL res = 0;
    while(b){
        if(b & 1) res = (res + a) % MOD;
        a = (a + a) % MOD;
        b >>= 1;
    }
    return res;
}
LL qpow(LL a,LL x,LL MOD){            //MOD可能很大,使用快速乘防止溢出
    LL res = 1;
    a %= MOD;
    while(x){
        if(x & 1) res = qmul(res,a,MOD);
        a = qmul(a,a,MOD);
        x >>= 1;
    }
    return res;
}
LL solve(){
    int len = cal();
    LL res = 1;
    for(int i = 0;i < len; ++i){
         LL MOD = mod * (p[i]-1);
         res = (((qpow(p[i],num[i]*b+1,MOD)-1+MOD)%MOD)/(p[i]-1) * res) % mod;
     }
    return res;
}
int main(){
    cin >> a >> b;                   //此题并没有a = 0 的数据
    if(a == 0 && b == 0) cout << 1 << '\n';
    else if(a <= 1) cout<< a << '\n';
    else cout<< solve() << '\n';
    return 0;
}

(2)递归计算1 + p + p^1 + ...... + p^n

设 sum(p,n) = 1 + p + p^1 + ...... + p^n,则有:

n为奇数:sum(p,n) = (1 + p^(n/2+1)) * sum(p,n/2)

n为偶数:sum(p,n) = (1 + p^(n/2+1)) * sum(p,n/2-1) + p^(n/2)

展开不难验证公式的正确性,复杂度为O(logn)

代码:

#include <iostream>
#include <algorithm>

using namespace std;
typedef long long LL;
const LL mod = 9901;
LL T,a,b,p[1000],num[1000];
int cal(){
    int len = 0;
    for(int i = 2;1LL*i*i <= a; ++i){
        if(a % i == 0){
            p[len] = i;
            while(a % i == 0){
                num[len]++;
                a /= i;
            }
            len++;
        }
    }
    if(a > 1){p[len] = a;num[len++] = 1;}
    return len;
}
LL qpow(LL a,LL x){
    LL res = 1;
    a %= mod;  
    while(x){
        if(x & 1) res = res * a % mod;
        a = a * a % mod;
        x >>= 1;
    }
    return res;
}
LL sum(LL p,LL n){
    if(n == 0) return 1;
    if(n & 1) return (1 + qpow(p,n/2+1)) * sum(p,n/2) % mod;
    else return ((1 + qpow(p,n/2+1)) * sum(p,n/2-1) % mod + qpow(p,n/2)) % mod;
}
LL solve(){
    int len = cal();
    LL res = 1;
    for(int i = 0;i < len; ++i)
        res = res * sum(p[i],num[i]*b) % mod;
    return res;
}
int main(){
    cin >> a >> b;
    if(a == 0 && b == 0) cout << 1 << '\n';
    else if(a <= 1) cout<< a << '\n';
    else cout<< solve() << '\n';
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值