描述
这是一篇面对初级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
总结
数学推导往往比代码数据结构优化更为重要