描述

这是一篇面对初级coder的题解。
知识点: 数学 快速幂 大数乘法 逆元(费马小定理)
难度: 三星


题解

题目:

,求f(x)%10000000033的值。


分析:
同上一题有相似之处 区别在于对数据长度的要求更高,输入已经变成了字符串

最近越来越多的协议会定义16字节长的整型,gcc 在 4.6 以上版本就可以使用 __int128_t & __uint128_t 了。

但需要注意的是,_uint128_t & __int128_t 仅对64位程序才有定义,因此如果编译选项中加入了 -m32,会出现找不到定义的编译错误。

另外 _uint128_t & __int128_t 并非c/c++ 标准,所以gcc目前只支持基本运算符的操作,输入输出这些都需要另外实现。

方法一:暴力求解

之前的乘方算法化简到最简单依旧需要计算两个大数乘法
同时由于由于取模运算的模太大所以必须对乘法进行处理,否则会溢出
long long 是8字节 两个long long相乘就到了是16字节,所以必须对乘法进行处理
类比竖式乘法:设计如下乘法逻辑,对每步乘法运算取模 保证乘法不溢出:

class Solution {
    const __int128_t mod=10000000033LL;//取余常数
public:
    __int128_t power(__int128_t base, __int128_t exponent) {
        if(exponent==0)
            return 1;
        __int128_t answer = 1;
        while(exponent){
            if(exponent&1)        //如果n的当前末位为1
                answer =muti(answer,base);  //ans乘上当前的a
            base =muti(base,base);        //a自乘
            exponent >>= 1;       //n往右移一位
        }
        return answer;
    }
    __int128_t muti(__int128_t a, __int128_t b) {//防止越界 乘法 每次只乘2字节
        __int128_t ans =0;
        while(b){
            ans=(ans+a*(b&0xFFFFLL))%mod;//相乘累加
            b>>=16;//乘数右移2字节
            a=(a<<16)%mod;//被乘数左移2字节
        }
        return ans;
    }
    __int128_t s2int128(string s) {
        __int128_t ans = 0, digit = 1;
        for(int i=s.size()-1;i>=0;digit*=10,i--) {
            ans += digit * (s[i]-'0');
        }
        return ans;
    }
    long long solve(string sa, string sb, int n) {
        if(n==0)
            return 0; //处理特殊情况
        __int128_t a=s2int128(sa);
        __int128_t b=s2int128(sb);
        __int128_t ans=0;
        for(int i=a;i<=b;i++){
            ans+=muti(n,power((__int128_t)n,(__int128_t)i));//按要求相加
            ans =ans % mod;
        }
        return (long long) ans;
    }
};
复杂度分析:
时间复杂度:快幂法的时间复杂度都是,同时由于没有做优化,总共需要进行次幂运算,所以时间复杂度为 
此处   乘法的次数是
空间复杂度 ,由于没有使用额外的空间 空间复杂度为 

运行测试:
运行时间 1001ms 占用内存 312KB
计算超时

方法二:数列求和+费马小定理逆元

数列求和
本题数列求和应该采用错位相减法,推导如下:
 
 两边同乘
式减式子得:

代入等比数列求和公式:
有:

化简得:

考虑费马小定理
根据上面的费马小定理有
即:
代入上式得


class Solution {
    const __int128_t mod=10000000033LL;//取余常数
public:
    __int128_t power(__int128_t base, __int128_t exponent) {
        if(exponent==0)
            return 1;
        __int128_t answer = 1;
        while(exponent){
            if(exponent&1)        //如果n的当前末位为1
                answer =muti(answer,base);  //ans乘上当前的a
            base =muti(base,base);        //a自乘
            exponent >>= 1;       //n往右移一位
        }
        return answer;
    }
    __int128_t muti(__int128_t a, __int128_t b) {//防止越界 乘法 每次只乘2字节
        __int128_t ans =0;
        while(b){
            ans=(ans+a*(b&0xFFFFLL))%mod;//相乘累加
            b>>=16;//乘数右移2字节
            a=(a<<16)%mod;//被乘数左移2字节
        }
        return ans;
    }
    __int128_t s2int128(string s) {//字符串转int128
        __int128_t ans = 0, digit = 1;
        for(int i=s.size()-1;i>=0;digit*=10,i--) {
            ans += digit * (s[i]-'0');
        }
        return ans;
    }
    long long solve(string sa, string sb, int n) {
        if(n==0)
            return 0; //处理特殊情况
        __int128_t a=s2int128(sa);
        __int128_t b=s2int128(sb);
        __int128_t ans=0;
        if(n==1)//特殊情况
            return muti(muti(a+1+b,b-a),power(2,mod-2));
        __int128_t xa = muti(a+1-muti(a,n)+mod,power(n, a));//xa=(a+1-ax)*x^a
        __int128_t xb = muti(b+1-muti(b,n)+mod,power(n, b));//xb=(b+1-bx)*x^b
        return muti((xa - xb + mod), power(n - 1, mod - 3));//
    }
};
复杂度分析:
时间复杂度:快幂法的时间复杂度都是,同时由于采用费马小定理和等比数列求和公式做了优化,总共需要进行3次幂运算,以及常数次快乘

空间复杂度:由于没有使用额外的空间 空间复杂度为 

运行测试:
运行时间2ms  占用内存404KB

总结

数学推导往往比代码数据结构优化更为重要