• AcWing 97. 约数之和


    题目传送门

    一、理论知识

    算术基本定理

    \[\large N=p_1^{\alpha_1}\cdot p_2^{\alpha_2}\cdot ... \cdot p_k^{\alpha_k} \]

    约数个数定理

    \[\large f(N)=\prod_{i=1}^{k}(a_i+1)= (\alpha_1+1)(\alpha_2+1)...(\alpha_k+1) \]

    证明:因为\(\large p_1^{\alpha_1}\)的约数有\(\large p_1^0,p_1^1,p_1^2,...,p_1^{\alpha_1}\),共\(\large a_1+1\)个,同理\(p_k^{\alpha_k}\)的约数有\(\large a_k+1\)个,根据乘法原理,就知道了约数的总个数。

    约数和定理

    \[\large Sum(A)=(1+p_1^1+p_1^2+...+p_1^{\alpha_1})(1+p_2^1+p_2^2+...+p_2^{\alpha_2})...(1+p_n^1+p_n^2+...+p_n^{\alpha_n}) \]

    简写

    \[\large Sum(A)=\prod_{i=1}^{n}(\sum_{j=0}^{\alpha_i}p_i^j) \]


    前置练习题

    约数之和

    分解质因数


    举栗子: \(180=2^2∗3^2∗5^1\)

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

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

    回到题目,在这个题目里的 约数和 就是如下式子:

    \[\large Ans=\prod_{i=1}^{n}(\sum_{j=0}^{\alpha_i*B}p_i^j) \]


    二、分治法

    分治法计算等比数列求和

    \(\large sum(p, k)=p^0+p^1+…+p^{k−1}\)

    • \(k\)为偶数时

    \[\large sum(p,k)=p^0+p^1+…+p^{k/2−1}+p^{k/2}+p^{k/2+1}+…+p^{k−1} \\ \Rightarrow \\ (p^0+p^1+…+p^{k/2−1})+p^{k/2}∗(p^0+p^1+…+p^{k/2−1}) \\ \Rightarrow \\ sum(p,k/2)+p^{k/2}∗sum(p,k/2) \\ \Rightarrow \\ p^{k/2+1}∗sum(p,k/2) \]

    • \(k\)为奇数时
      为了更方便调用我们写的偶数项情况,可以单独拿出最后一项,把剩下的项转化为求偶数项的情况来考虑,再加上最后一项,就是奇数项的情况了。也即\(sum(p,k−1)+p^{k−1}\)
    //sum(p,k)=p^0 + p^1 .. + p^{k-1}
    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;
    
    //分解质因数
    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;
    }
    
    // p0 + .. + pk-1
    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() {
        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; //约数和公式,需要到每个质因子的it.second次方*B
    
            // res要乘上每一项, 注意这里是k + 1
            res = (LL)res * sum(p, k + 1) % mod;
        }
        if (!A) res = 0; //还要特判A是不是0
        printf("%d\n", res);
        return 0;
    }
    

    时间复杂度 \(O(\sqrt{n}lognlogn)\)


    三、公式法

    等比数列求和

    \[\large p^0+p^1+p^2+...+p^{k-1}=\frac{p^{k}-1}{p-1} \]

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

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

    • 分母\(p-1\)\(mod\)的倍数,不存在逆元,这时直接乘\((1+p^1+…p^k)\% mod\), 即 \(k+1\)

    • 分母\(p-1\)不是\(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(\sqrt{n}logn)\)


  • 相关阅读:
    javap,是 java printer 的缩写,是 JDK 自带的 Java 字节码分析工具
    这段代码的返回值在出现异常和不出现异常的情况下,分别应该是多少?
    strictfp 关键字修饰方法,即 strict float point (精确浮点)
    transient 修饰符修饰属性:不需要序列化的属性
    Java 中的 CAS 操作
    ReentrantLock 与 synchronized 的比较
    线程安全的实现方法:互斥同步、非阻塞同步、无同步方案
    J.U.C包的意义
    synchronized 原理
    什么场景下,使用 final、volatile、Atomic原子类、synchronized、J.U.C 包中的锁?
  • 原文地址:https://www.cnblogs.com/littlehb/p/16411168.html
Copyright © 2020-2023  润新知