AcWing 97. 约数之和

AcWing 97. 约数之和

一、题目描述

假设现在有两个自然数 ABSAB 的所有约数之和。

请你求出 S mod 9901 的值是多少。

输入格式
在一行中输入用空格隔开的两个整数 AB

输出格式
输出一个整数,代表 S mod 9901 的值。

数据范围
0A,B5×107

输入样例:

2 3

输出样例:

15

注意: AB 不会同时为 0

二、理论知识

唯一分解

N=p1α1p2α2...pkαk

约数个数

(α1+1)(α2+1)...(αk+1)

证明:因为p1α1的约数有p10,p11,p12,...,p1α1,共a1+1个,同理pkαk的约数有ak+1个,根据乘法原理,就知道了约数的总个数。

约数和

(1+p11+p12+...+p1α1)(1+p21+p22+...+p2α2)...(1+pn1+pn2+...+pnαn)

证明p1a1 的约数有p10,p11,p12...p1a1a1+1个,p2a2...pkak 同理,n的一个约数就是在p1a1,p2a2,p3a3...pkak 每一个的约数中任挑一个组成的,挑法的个数就是约数个数,根据乘法原理他们的和为(p10+p11+...+p1a1)(p20+p21+...+p2a2)...(pk0+pk1+...+pkak)

练习题

约数之和

分解质因数

举栗子: 180=223251

约数个数:(2+1)(2+1)(1+1)=18

约数和:(1+2+4)(1+3+9)(1+5)=546

三、分治法求解等比数列求和

巧妙的数学变换

sum(p,k)=p0+p1++pk1

  • k为偶数

sum(p,k)=p0+p1++pk/21+pk/2+pk/2+1++pk1(p0+p1++pk/21)+pk/2(p0+p1++pk/21)sum(p,k/2)+pk/2sum(p,k/2)(pk/2+1)sum(p,k/2)

解释:这样我们就得到了一个递推式,

  • pk/2 可以用快速幂计算
  • sum(p,k/2) 由于我们的递推是从小到大推的,在推sum(p,k)时,sum(p,k/2)
    经算完了,可以直接用。

  • k为奇数
    为了更方便调用我们写的偶数项情况,可以单独拿出最后一项,把剩下的项转化为求偶数项的情况来考虑,再加上最后一项,就是奇数项的情况了。也即

    sum(p,k)=p0+p1+p2+...+pk1=sum(p,k1)+pk1

    当然,也可以拿出来第一项,就是

    sum(p,k)=p0+p1+p2+...+pk1=p0+p×(p0+p1+...+pk2)=1+p×sum(p,k1)

下面的代码实现中,我将使用拿出最后一项的办法:

int sum(int p, int k) {
    if(k == 1) return 1;  //边界
    if(k % 2 == 0)  
        return (LL)(qmi(p, k / 2) + 1) * sum(p, k / 2) % mod;
    return (qmi(p, k - 1) + sum(p, k - 1)) % mod;
}

实现代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int mod = 9901;
int A, B;

// 分解质因数
unordered_map<int, int> primes; // map存的是数对,key+value,默认按key由小到大排序
void divide(int x) {
    for (int i = 2; i * i <= x; i++)
        while (x % i == 0) primes[i]++, x /= i;
    if (x > 1) primes[x]++;
}

// 快速幂
int qmi(int a, int b) {
    int res = 1;
    while (b) { // 对b进行二进制分解(我给起的名),枚举b的每一个二进制位
        if (b & 1) res = (LL)res * a % mod;
        a = (LL)a * a % mod;
        b >>= 1;
    }
    return res;
}

// 分治法求等比数列求和公式
int sum(int p, int k) {
    if (k == 1) return 1; // 递归出口
    if (k % 2 == 0)       // 偶数
        return (LL)(qmi(p, k / 2) + 1) * sum(p, k / 2) % mod;
    return (qmi(p, k - 1) + sum(p, k - 1)) % mod; // 奇数
}

int main() {
    cin >> A >> B;
    // 对A分解质因子
    divide(A);

    int res = 1;
    for (auto it : primes) {
        // p是质因子,k是质因子的次数
        int p = it.first, k = it.second * B; // 约数和公式,需要到每个质因子的it.second次方*B

        // res要乘上每一项, 注意这里是k + 1
        res = (LL)res * sum(p, k + 1) % mod; // 满满的套路感
    }
    if (!A) res = 0; // 还要特判A是不是0,注意思考数据边界
    cout << res << endl;
    return 0;
}

时间复杂度 O(nlognlogn)

四、等比数列求和公式

p0+p1+p2+...+pk1=pk1p1

除了分治法外,还可以用 乘等比数列和公式 得出分子,再 乘分母逆元 的做法:

快速幂求逆元
这样的话,就可以愉快地套用高中学的等比数列和公式来求每一项,但这里需要 特判逆元不存在 的情况:

  • 分母p1mod的倍数,不存在逆元,这时直接乘(1+p1+pk)%mod, 即 k+1

  • 分母p1不是mod的倍数,存在 逆元,这是需要用 快速幂求分子,再用 快速幂求分母的逆元,两者相乘就得到了每一项

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int mod = 9901;
int A, B;

//分解质因数
map<int, int> primes; // map存的是数对,key+value,默认按key由小到大排序
void divide(int x) {
    for (int i = 2; i <= x / i; i++)
        while (x % i == 0) primes[i]++, x /= i;
    if (x > 1) primes[x]++;
}

//快速幂
int qmi(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = (LL)res * a % mod;
        a = (LL)a * a % mod;
        b >>= 1;
    }
    return res;
}

int main() {
    scanf("%d %d", &A, &B);
    //对A分解质因子
    divide(A);

    int res = 1;
    for (auto it : primes) {
        // p是质因子,k是质因子的次数
        int p = it.first, k = it.second * B;
        // res要乘上每一项, 注意这里是k + 1
        if ((p - 1) % mod == 0) {
            //不存在逆元,由于p-1的是mod的倍数, 故p%mod=1
            //所以1 + p + ... + p^k每个数%mod都是1,共k + 1个数,总就是k + 1
            res = (LL)res * (k + 1) % mod;
        } else
            //分子用快速幂计算,注意标准公式和此题的区别,k+1
            //分母用费马小定理求逆元 qmi(p-1,mod-2)
            res = (LL)res * (qmi(p, k + 1) - 1) % mod * qmi(p - 1, mod - 2) % mod;
    }
    if (!A) res = 0;
    printf("%d\n", (res % mod + mod) % mod);
    return 0;
}

时间复杂度O(nlogn)

posted @   糖豆爸爸  阅读(137)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2019-06-25 centos7设置sshd端口,firewall,selinux设置
2018-06-25 异构平台同步(mysql-->oracle)
2015-06-25 管理软件最新版本
2013-06-25 运行教学平台的主机在不用一段时间后,再次连接服务器,报告无法连接的解决办法
Live2D
点击右上角即可分享
微信分享提示