POJ 1845 Sumdiv <数论(逆元 / 二分递归)>

题目传送门

题目大意:输入两个数 AB AB 的所有因子之和。

分析:
这道题折腾了很久啊,值得写一写报告。
首先一个大于 1 的正整数X有唯一分解:

X=pe11pe22...pennpn

那么 X 的所有因子之和为:
sum=(1+p11+p21+...+pe11)(1+p12+p22+...+pe22)...(1+p1n+p2n+...+penn)

所以我们先求 A 的素因子分解,然后答案就是:
ans=(1+p11+p21+...+pe1B1)(1+p12+p22+...+pe2B2)...(1+p1n+p2n+...+penBn)%mod

求多项式 (1+x1+x2+...+xn)%mod 有两种方法:
1)递归二分:
n 为奇数时,
sum%mod=(1+x1+x2+...+xn/2)(1+xn/2+1)%mod
可以看到,前面是原式的一半,可以递归求解,而后面是指数式,可以用快速幂算法。
n 为偶数时,
sum%mod=((1+x1+x2+...+xn/21)(1+xn/2+1)+xn/2)%mod
同样使用递归和快速幂求解。
2)等比通项:
显然
sum%mod=xn+11x1%mod
形如 ab%mod 的分式取模也有两种方法,一种是求分母 b 在模mod意义下的逆元,那么 ab%mod=ab1%mod ,这里因为 mod=9901 是素数,所以由欧拉定理: bϕ(mod)1%mod 可知 b1bmod2%mod 另一种是变换模值,即 ab%mod=a%(modb)b%mod ,在本题中,有可能某些素因子 p 满足(p1)%mod==0所以此时 p1 不存在模 mod 意义下的逆元,此时只能使用变换模值法,而使用变换模值法时要注意 modb 有可能很大,在后续计算时会造成溢出错误,这里我们采用 %mod 逐位连乘的技巧,具体看代码吧。

ps:在一篇题解中,大神给出了求 1p(p) p 的所有逆元的一个递推式,用这个递推式就可以在O(p)复杂度里求逆元了。这对于 p <script type="math/tex" id="MathJax-Element-165">p</script>很大时是很有用的。

代码

/*等比通项*/
#include <iostream>
#include <cstring>
#include <string>
using namespace std;

typedef long long LL;
LL a,b;
LL p,e;
LL m=9901;

LL multi(LL a,LL b,LL mod){
    LL res=0;
    a%=mod;
    b%=mod;
    while(b){
        if(b&1) res=(res+a)%mod;
        a=(a*2)%mod;
        b>>=1;
    }
    return res;
}

LL power(LL a,LL b,LL mod){
    LL res=1;
    a%=mod;
    while(b){
        if(b&1) res=multi(res,a,mod);
        a=multi(a,a,mod);
        b>>=1;
    }
    return res; 
}

LL sum(LL p,LL n, mod){
    if(!n) return 1;
    else{
        if(n&1) return sum(p,n/2)*(power(p,n/2+1,mod)+1)%mod;
        else return sum(p,n/2-1)*(power(p,n/2+1,mod)+1)*power(p,n/2,mod)%mod;
    }
}

void solve(){
    LL ans=1;
    for(int i=2;i*i<=a;){
        if(a%i==0){
            p=i;
            e=0;
            while(!(a%i)){
                e++;
                a/=i;
            }
            if((p-1)%m==0){
                LL M=m*(p-1); 
                ans=ans*(power(p,e*b+1,M)+M-1)%M/(p-1)%m;
            } 
            else ans=ans*(power(p,e*b+1,m)-1)*power(p-1,m-2,m)%m;
        }
        if(i==2) i++;
        else i+=2;
    }
    if(a>1){
        p=a; e=1;
        if((p-1)%m==0){
            LL M=m*(p-1);
            ans*=(power(p,e*b+1,M)+M-1)%M/(p-1)%m;
        } 
        else ans=ans*(power(p,e*b+1,m)-1)*power(p-1,m-2,m)%m;
    }
    cout<<(ans+m)%m<<endl;
}

int main(){
    while(cin>>a>>b){
        solve();
    }
    return 0;
}
/*递归二分*/
#include <iostream>
#include <cstring>
#include <string>
using namespace std;

typedef long long LL;
LL a,b;
LL p,e;
LL mod=9901;

LL power(LL a,LL b){
    LL res=1;
    a%=mod;
    while(b){
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res; 
}

LL sum(LL p,LL n){
    if(!n) return 1;
    else{
        if(n&1) return sum(p,n/2)*(power(p,n/2+1)+1)%mod;
        else return (sum(p,n/2-1)*(power(p,n/2+1)+1)+power(p,n/2))%mod;
    }
}

void solve(){
    LL ans=1;
    for(int i=2;i*i<=a;){
        if(a%i==0){
            p=i;
            e=0;
            while(!(a%i)){
                e++;
                a/=i;
            }
            ans=ans*sum(p,e*b)%mod;
        }
        if(i==2) i++;
        else i+=2;
    }
    if(a>1){
        p=a; e=1;
        ans=ans*sum(p,e*b)%mod;
    }
    cout<<(ans+mod)%mod<<endl;
}

int main(){
    while(cin>>a>>b){
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值