文章目录

  • 1 问题
    • 1.1 题面描述
    • 1.2 输入描述
    • 1.3 输出描述
    • 1.4 样例描述
    • 1.5 样例解释
  • 2 分析
    • 2.1 数学抽象
    • 2.2 解决方法
      • 2.2.1 等比求和
        • 2.2.1.1 求和公式:分数取模——乘法逆元
        • 2.2.1.2 二分递归:数学推导——分治思想
      • 2.2.2 求解 q n q^{n} qn ——快速幂取模
      • 2.2.3 int64 的乘法模运算——位运算法
  • 3 实现
    • 3.1 求和公式
    • 3.2 二分递归
  • 4 参考文章

1 问题

7-2 国王的奖励

1.1 题面描述

国际象棋是很久以前由一个印度人 Shashi 发明的,当他把该发明献给国王的时候,国王很高兴,就许诺可以给这个发明人任何他想要的奖赏。Shashi 要求以这种方式给他一些粮食:棋盘的第 1 个方格里只放 1 粒麦粒,第 2 格 q 个,第 3 格 q2 个,第 4 格 q3 个……,直到 n 个格子全部放满。这个奖赏最终会是什么样子的呢?Shashi 已经有算法了,请你算一算吧。
当然 Shashi 并不关心具体的数有多少了,他有一个检验你的答案是否与他心意相通的办法:把你求出的答案对 100000007 取模看看和 Shashi 算的是否一样就行了。

1.2 输入描述

本题输入具有多组样例
第一行为一个数 t (1 ≤ t ≤ 500),代表测试数据的组数。
之后的 t 行,每行两个数 q, n (1 ≤ q, n ≤ 1018),含义见题目描述。

1.3 输出描述

输出共 t 行,每行一个数,为你算的答案。
友情提醒:你算的答案应该比 100000007 小。

1.4 样例描述

样例输入:

3
2 1
2 2
2 3

样例输出:

1
3
7

1.5 样例解释

对于样例:

  • q = 2, n = 1 时仅有1个麦粒
  • q = 2, n = 2 时有 1 + 2 = 3 个麦粒
  • q = 2, n = 3 时有 1 + 2 + 22 = 7 个麦粒

作者|cugbacm
单位|中国地质大学(北京)
代码长度限制|16 KB
时间限制|1000 ms
内存限制|256 MB

2 分析

2.1 数学抽象

该问题本质为等比数列求和问题, 1 1 1 为首项, q q q 为公比, n n n 为项数。
需要注意:

  • 每次输入的 q q q, n n n 可能不同;
  • q q q, n n n 的范围超过 int32,需要用 int64 来存储;
  • 结果要对 100000007 100000007 100000007 取模;
  • 计算过程中涉及到大数乘法,直接用 * 运算符会造成数据溢出,需要用自己设计的大数乘法模运算作为替代。

2.2 解决方法

2.2.1 等比求和

等比数列求和方法一般有直接求和、二分递归以及运用等比求和通项公式。
对于 q = 1 q=1 q=1 的情况,显然等比求和通项公式是最好的选择,取模对此并无影响,时间复杂度为 O ( 1 ) \Omicron(1) O(1),因此问题集中在 q > 1 q>1 q>1 的情况之上。

本文中我们将依次对 q > 1 q>1 q>1 情况下的等比求和通项公式法二分递归法进行讨论分析。

2.2.1.1 求和公式:分数取模——乘法逆元

等比数列求和通项公式如下:
S n = 1 − q n 1 − q S_{n}=\cfrac{1-q^n}{1-q} Sn​=1−q1−qn​
求和部分时间复杂度只有 O ( 1 ) \Omicron(1) O(1),主要取决于求解 q n q^n qn 的算法。

但模运算对除法不存在分配率,即
a m o d p b m o d p ≠ a b m o d p \dfrac{a \bmod p}{b \bmod p} \ne \cfrac a b \bmod p bmodpamodp​=ba​modp
不能直接使用通项公式。

对分数取模有两种方法:乘法逆元和变换模值,我们在此仅对逆元进行讨论,变换模值可参见参考文章1。

若在 m o d p \bmod~p mod p 意义下,对于一个整数 a a a,有 a ⋅ b ≡ 1 ( m o d p ) a \cdot b \equiv 1 \pmod p a⋅b≡1(modp),那么这个整数 b b b 与 a a a 互为乘法逆元

求乘法逆元的方法主要为:将 a ⋅ b ≡ 1 ( m o d p ) a \cdot b \equiv 1 \pmod p a⋅b≡1(modp) 转化为 a x + p y = 1 ax+py=1 ax+py=1,用拓展欧几里得算法解二元一次方程解出 x x x。
但本题中 p = 100000007 p=100000007 p=100000007 是质数,因此可以用费马小定理(详见参考文章3)直接求解。

费马小定理:
若 a a a 为整数, p p p 为质数,则有
a p m o d p ≡ a a^p \bmod p \equiv a apmodp≡a
当 a a a 不是 p p p 的倍数时,可写为
a p − 1 m o d p ≡ 1 a^{p-1} \bmod p \equiv 1 ap−1modp≡1

故有
a b m o d p = ( b p − 1 m o d p ) ( a b m o d p ) = a ⋅ b p − 2 m o d p \cfrac a b \bmod p=(b^{p-1} \bmod p)(\cfrac a b \bmod p)=a \cdot b^{p-2} \bmod p ba​modp=(bp−1modp)(ba​modp)=a⋅bp−2modp


S n = ( 1 − q n ) ( 1 − q ) p − 2 m o d p S_n=(1-q^n)(1-q)^{p-2} \bmod p Sn​=(1−qn)(1−q)p−2modp

注意若 b b b 是 p p p 的倍数则不能用逆元。因为此时 b m o d p ≡ 0 b \bmod p \equiv 0 bmodp≡0,无法求解逆元。

代码实现如下:

// q: 公比; n:项数; mod: 模; qpow: 求幂函数
if (q == 1) {printf("%lld\n", n % mod);
} else {printf("%lld\n", (qpow(q, n) - 1) * qpow(q - 1, mod - 2) % mod);
}

2.2.1.2 二分递归:数学推导——分治思想

如前文所述,当 b b b 是 p p p 的倍数时,无法求解逆元。
当数据中存在上述关系时,我们需要另寻他法,此处选择二分递归法进行等比求和。

要进行二分递归,我们首先要进行数学推导,得出等比数列前 n n n 项和 S n S_n Sn​ 的递推公式。运用分治思想,我们对 S n S_n Sn​ 进行分类讨论:

S n = ( q 0 + q 1 + q 2 + ⋯ + q n − 1 ) m o d p ( n ⩾ 1 ) S_n=(q^0+q^1+q^2+\dotsb+q^{n-1}) \bmod p \mskip0.8em (n \geqslant 1) Sn​=(q0+q1+q2+⋯+qn−1)modp(n⩾1)

当 n = 1 n=1 n=1 时,有
S 1 = q 0 = 1 S_1=q^0=1 S1​=q0=1

当 n > 1 n>1 n>1 时,有
S n = { [ ( 1 + q n − 1 2 ) S n − 1 2 + q n − 1 ] m o d p ( n 为奇数 ) ( 1 + q n 2 ) S n 2 m o d p ( n 为偶数 ) S_n=\begin{cases} [(1+q^{\tfrac {n-1} 2})S_{\tfrac {n-1} 2}+q^{n-1}] \bmod p &(n\text{为奇数})\\\\ (1+q^{\tfrac {n} 2})S_{\tfrac n 2} \bmod p &(n\text{为偶数}) \end{cases} Sn​=⎩ ⎨ ⎧​[(1+q2n−1​)S2n−1​​+qn−1]modp(1+q2n​)S2n​​modp​(n为奇数)(n为偶数)​

该算法求和部分时间复杂度为 O ( log ⁡ n ) \Omicron(\log n) O(logn),若求解 q n q^n qn 的算法时间复杂度为 O ( x ) \Omicron(x) O(x),则总时间复杂度为 O ( x log ⁡ n ) \Omicron(x\log n) O(xlogn)。

代码实现如下:

// q: 公比; n: 项数; mod: 模; qpow: 求幂函数; qmul: 求积函数
long long sum(long long q, long long n) {if (n == 1) {return 1;}// 即 n % 2 == 1if (n & 1) {// 整型除法具有向下取整的特点// n 为奇数时, (n - 1) / 2 === n / 2, 可据此简化代码// n >> 1 即 n / 2return (qmul(1 + qpow(q, n >> 1), sum(q, n >> 1)) + qpow(q, n - 1)) % mod;} else {return qmul(1 + qpow(q, n >> 1), sum(q, n >> 1));}
}

2.2.2 求解 q n q^{n} qn ——快速幂取模

解决了等比求和的问题,就只剩求解 q n q^n qn 了。
此处选择了位运算的快速幂取模算法,时间复杂度为 O ( log ⁡ n ) \Omicron(\log n) O(logn)。

代码实现如下:

// base: 底数; power: 指数; mod: 模; qmul: 求积函数
long long qpow(long long base, long long power) {// 对指数的底先取模,减少运算次数base %= mod;long long ans = 1;while (power) {// 即 power % 2 == 1if (power & 1) {ans = qmul(ans, base);}base = qmul(base, base);// 即 power /= 2power >>= 1;}return ans;
}

2.2.3 int64 的乘法模运算——位运算法

这部分优化直接借用参考文章4,将大数乘法的时间复杂度从反复翻倍法(按位相乘)的 O ( log ⁡ n ) \Omicron(\log n) O(logn) 降到了 O ( 1 ) \pmb{\red{\Omicron(1)}} O(1)O(1) !!

代码实现如下:

// a, b: 乘数; mod: 模
long long qmul(long long a, long long b) {// 前两句处理了负数和数值过大的情况a = (a % mod + mod) % mod;b = (b % mod + mod) % mod;long long c = a * (long double) b / mod;long long ans = a * b - c * mod;// 若结果不在 [0, mod) 区间则调整一下if (ans < 0) {ans += mod;} else if (ans >= mod) {ans -= mod;}return ans;
}

3 实现

3.1 求和公式

完整代码实现如下:
总时间复杂度为 O ( log ⁡ n ) \Omicron(\log n) O(logn)

#include<stdio.h>
#define mod 100000007long long qmul(long long a, long long b) {a = (a % mod + mod) % mod;b = (b % mod + mod) % mod;long long c = a * (long double) b / mod;long long ans = a * b - c * mod;if (ans < 0) {ans += mod;} else if (ans >= mod) {ans -= mod;}return ans;
}long long qpow(long long base, long long power) {base %= mod;long long ans = 1;while (power) {if (power & 1) {ans = qmul(ans, base);}base = qmul(base, base);power >>= 1;}return ans;
}int main() {int t;scanf("%d", &t);long long q, n;while (t--) {scanf("%lld %lld", &q, &n);if (q == 1) {printf("%lld\n", n % mod);} else {printf("%lld\n", (qpow(q, n) - 1) * qpow(q - 1, mod - 2) % mod);}}return 0;
}

提交结果如下:
7ms,低到惊人的耗时!

3.2 二分递归

完整代码实现如下:
总时间复杂度为 O ( log ⁡ 2 n ) \Omicron(\log^2 n) O(log2n)

#include<stdio.h>
#define mod 100000007long long qmul(long long a, long long b) {a = (a % mod + mod) % mod;b = (b % mod + mod) % mod;long long c = a * (long double) b / mod;long long ans = a * b - c * mod;if (ans < 0) {ans += mod;} else if (ans >= mod) {ans -= mod;}return ans;
}long long qpow(long long base, long long power) {base %= mod;long long ans = 1;while (power) {if (power & 1) {ans = qmul(ans, base);}base = qmul(base, base);power >>= 1;}return ans;
}long long sum(long long q, long long n) {if (n == 1) {return 1;}if (n & 1) {return (qmul(1 + qpow(q, n >> 1), sum(q, n >> 1)) + qpow(q, n - 1)) % mod;} else {return qmul(1 + qpow(q, n >> 1), sum(q, n >> 1));}
}int main() {int t;scanf("%d", &t);long long q, n;while (t--) {scanf("%lld %lld", &q, &n);if (q == 1) {printf("%lld\n", n % mod);} else {printf("%lld\n", sum(q, n));}}return 0;
}

提交结果如下:
相较于等比求和通项公式法,二分法耗时明显增加,但依旧相当快速且适用范围更广。

4 参考文章

  1. 《poj 1845 Sumdiv 数论–等比数列和(逆元或者递归)》
  2. 《等比数列求和,二分递归》
  3. 《费马小定理(易懂)》
  4. 《小技巧1——长整型:64位整数的乘法模运算》

【PTA】7-2 国王的奖励——分数取模、分治思想、快速幂、int64的乘法模运算【C/C++】相关推荐

  1. 【面试相关】python实现快速幂取余算法详解

    假设我们要计算 2102^{10}210 对1000取模的结果,可以很简单的得到24.但是如果要求 210002^{1000}21000 对1000取模的结果,常规方法就行不通了,因为常规的变量无法容 ...

  2. 快速幂(取余) c++

    题意:求a的b次幂对p取余的结果 快速幂做法: #include<iostream> using namespace std; int main() {long long a,b,p;ci ...

  3. 快速幂(快速幂取余)

    引入公式 (a*b) %c = ((a % c)*(b % c)) %c 普通求幂的解法 public static int pow(int x,int n) {int result = 1;for ...

  4. 保研机试——2数学问题(简单数学、最大公约/最小公倍、分数运算、素数、质因子分解、快速幂、高精度问题、常见数学公式总结、规律神器OEIS)

    1 简单数学 2 最大公约/最小公倍 3 分数运算 4 素数 5 快速幂 5 高精度问题 6 常见数学公式总结 7 规律神器OEIS 1 简单数学 (1)同余模定理:所谓的同余,顾名思义,就是许多的数 ...

  5. C语言快速幂取模算法小结

    资料链接:http://www.jb51.net/article/54947.htm C语言实现的快速幂取模算法,是比较常见的算法.分享给大家供大家参考之用.具体如下: 首先,所谓的快速幂,实际上是快 ...

  6. 洛谷——P1226 取余运算||快速幂

    P1226 取余运算||快速幂 题目描述 输入b,p,k的值,求b^p mod k的值.其中b,p,k*k为长整型数. 输入输出格式 输入格式: 三个整数b,p,k. 输出格式: 输出"b^ ...

  7. 数学--数论--HDU 4675 GCD of Sequence(莫比乌斯反演+卢卡斯定理求组合数+乘法逆元+快速幂取模)

    先放知识点: 莫比乌斯反演 卢卡斯定理求组合数 乘法逆元 快速幂取模 GCD of Sequence Alice is playing a game with Bob. Alice shows N i ...

  8. 洛谷 1226 取余运算||快速幂

    洛谷  取余运算||快速幂 1226 其实比起楼下的大佬们,我主要是多了些位运算和讲解. 想法一: 直接输出 pow(b,q)%k 嗯~~勇气可嘉,但是看一眼数据范围(长整型)就会意识到,这个方法也许 ...

  9. 多项式的基础操作(逆元/除法/取模/对数ln/开根sqrt/指数exp/快速幂)带模板+luogu全套例题

    文章目录 多项式的逆元 理论推导 模板 例题:[luogu P4238][模板]多项式乘法逆 题目 code 多项式的除法/取模 理论推导 多项式牛顿迭代法 模板 例题:[luoguP4512][模板 ...

最新文章

  1. Linux与shell环境,Linux 环境及 Shell 程序
  2. C#自定义控件在添加引用后不显示在工具箱的解决方法
  3. Use Chunks.groupsIterable and filter by instanceof Ent rypoint instead
  4. The fall of RNN / LSTM
  5. HDU_2795 Billboard(线段树)
  6. 通过Dapr实现一个简单的基于.net的微服务电商系统(十七)——服务保护之动态配置与热重载...
  7. Java中父类的私有数据和静态数据在内存中是如何存储的?
  8. Introduction to Web MIDI
  9. form表单会跨域_form 表单跨域提交
  10. 数据库安全性概念与自主安全性机制
  11. [你的灯亮着吗]读书笔记
  12. 飞驴更新纪录,一定超过他。
  13. snap7/Qt/ros-------ubantu14.04下杂记
  14. 哈希摘要、证书、对称密钥、公私密钥应用场景梳理
  15. 单片机音频谱曲软件_单片机谱曲软件怎么弄 51单片机蜂鸣器电子琴程序
  16. 虚拟主机是干什么用的
  17. logstash 配置
  18. 计算机整理碎片有用吗,电脑磁盘碎片整理有什么用?需要经常整理吗?
  19. php入侵代码,入侵PHP网站就这么简单.pdf
  20. 【uniapp滚动穿透】 在u-modal中使用scroll-view底下主页面会跟随滚动

热门文章

  1. 使用Python检测符号及乱码字符
  2. 根据银行卡号验证账号属于哪个开户行简单js验证
  3. java引入包_java如何导入包
  4. MATLAB教程三:MATLAB程序流程控制
  5. word怎么设置边距为80磅_上边距80磅,下边距82磅是多少
  6. How To Be Successful(by Sam Altman)
  7. websocket接口自动化集成pytest测试框架
  8. 工欲利其器: sqlyog 智能执行功能详解.(http://my.oschina.net/phpnew/blog/151194)
  9. 选择软件组成分析工具的最佳做法
  10. Android实现MQTT客户端