第三章 循环结构练习
❥(^_-)第一题 a ∧ b a\land b a∧b 【acwing 89】
求 a 的 b 次方对 p 取模的值。
输入格式: 三个整数
a
,
b
,
p
a,b,p
a,b,p ,在同一行用空格隔开。
输出格式:输出一个整数,表示
a
ˆ
b mod p
\color{red}\colorbox{lavender}{a \^ b mod p}
a ˆb mod p的值。
数据范围: 0 ≤ a , b ≤ 1 0 9 0\le a,b\le 10^9 0≤a,b≤109 ; 1 ≤ p ≤ 1 0 9 1 \le p \le 10^9 1≤p≤109
输入样例1:
3 2 7
\colorbox{lavender}{3 2 7}
3 2 7
输出样例1:
2
\colorbox{lavender}{2}
2
输入样例2:
5201314 0 1
\colorbox{lavender}{5201314 0 1}
5201314 0 1
输出样例2:
0
\colorbox{lavender}0
0
输入样例3:
131452020 5201314 1314
\colorbox{lavender}{131452020 5201314 1314}
131452020 5201314 1314
输出样例3:
486
\colorbox{lavender}{486}
486
思路: 本题需要使用快速幂的思想。下面简述一下快速幂是如何进行计算的,假设我们现在需要求解
2
31
2^{31}
231的结果。计算过程如下所示:
2
31
=
1
×
2
31
写成
k
×
a
b
的形式
=
1
×
(
2
×
2
30
)
因为上一步的
b
为奇数,所以把
a
b
拆分成
a
×
a
b
−
1
的形式
=
2
×
2
30
将上一步拆分出来的
a
合并到
k
中,即
k
=
k
×
a
=
2
×
(
2
2
)
15
因为上一步的
b
为偶数
,
所以此处变形为
(
a
2
)
b
2
的形式
=
2
×
4
15
合并上一步的操作,即
a
=
a
2
,
b
=
b
2
=
2
×
(
4
×
4
14
)
上一步的
b
是奇数,所以拆分一个
a
出来,指数减
1
=
8
×
4
14
=
8
×
(
4
2
)
7
因为上一步指数部分
b
是偶数,所以底数翻倍,指数除以
2
=
8
×
1
6
7
=
8
×
(
16
×
1
6
6
)
因为上一步指数部分
b
是奇数,所以拆分一个
a
出来,指数减
1
=
128
×
1
6
6
=
128
×
(
1
6
2
)
3
因为上一步指数部分
b
是偶数,所以底数翻倍,指数除以
2
=
128
×
25
6
3
=
128
×
(
256
×
25
6
2
)
因为上一步指数部分
b
是奇数,所拆分一个
a
出来,指数减
1
=
32768
×
25
6
2
=
32768
×
(
25
6
2
)
1
因为上一步指数
b
是偶数,所以底数翻倍,指数除以
2
=
32768
×
6553
6
1
=
32768
×
65536
×
6553
6
0
因为上一步指数部分为奇数,所以拆分一个
a
出来,指数减
1
=
2147483648
×
6553
6
0
指数部分为
0
,结束操作。
=
2147483648
×
1
=
2147483648
\begin{aligned} 2 ^{31} &= 1 \times 2^{31} \hspace{3em}写成k \times a^b的形式 \\ &= 1 \times ( 2 \times 2^{30} ) \hspace{3em} 因为上一步的b为奇数,所以把a^b拆分成 a \times a^{b-1}的形式\\ &=2 \times 2^{30} \hspace{3em}将上一步拆分出来的a合并到k中,即 k=k \times a\\ &= 2 \times(2^2)^{15} \hspace{3em} 因为上一步的b为偶数,所以此处变形为(a^2)^{\dfrac{b}{2}}的形式\\ &=2 \times 4^{15} \hspace{3em}合并上一步的操作,即a=a^2,b=\dfrac{b}{2}\\ &= 2 \times ( 4 \times 4^{14} ) \hspace{3em}上一步的b是奇数,所以拆分一个a出来,指数减1 \\ &= 8 \times 4^{14} \\ &= 8 \times (4^2)^7 \hspace{3em}因为上一步指数部分b是偶数,所以底数翻倍,指数除以2\\ &= 8 \times16^7 \\ &= 8 \times (16 \times 16^6) \hspace{3em}因为上一步指数部分b是奇数,所以拆分一个a出来,指数减1 \\ &= 128 \times 16^6 \\ &= 128 \times (16^2)^3 \hspace{3em}因为上一步指数部分b是偶数,所以底数翻倍,指数除以2 \\ &= 128 \times 256^3 \\ &= 128 \times (256 \times 256^2) \hspace{3em}因为上一步指数部分b是奇数,所拆分一个a出来,指数减1 \\ &= 32768 \times256^2 \\ &= 32768 \times (256^2)^1 \hspace{3em}因为上一步指数b是偶数,所以底数翻倍,指数除以2\\ &= 32768 \times 65536^1 \\ &= 32768 \times 65536 \times 65536^0 \hspace{3em}因为上一步指数部分为奇数,所以拆分一个a出来,指数减1\\ &= 2147483648 \times 65536^0 \hspace{3em}指数部分为0,结束操作。\\ &= 2147483648 \times 1 \\ &= 2147483648 \end{aligned}
231=1×231写成k×ab的形式=1×(2×230)因为上一步的b为奇数,所以把ab拆分成a×ab−1的形式=2×230将上一步拆分出来的a合并到k中,即k=k×a=2×(22)15因为上一步的b为偶数,所以此处变形为(a2)2b的形式=2×415合并上一步的操作,即a=a2,b=2b=2×(4×414)上一步的b是奇数,所以拆分一个a出来,指数减1=8×414=8×(42)7因为上一步指数部分b是偶数,所以底数翻倍,指数除以2=8×167=8×(16×166)因为上一步指数部分b是奇数,所以拆分一个a出来,指数减1=128×166=128×(162)3因为上一步指数部分b是偶数,所以底数翻倍,指数除以2=128×2563=128×(256×2562)因为上一步指数部分b是奇数,所拆分一个a出来,指数减1=32768×2562=32768×(2562)1因为上一步指数b是偶数,所以底数翻倍,指数除以2=32768×655361=32768×65536×655360因为上一步指数部分为奇数,所以拆分一个a出来,指数减1=2147483648×655360指数部分为0,结束操作。=2147483648×1=2147483648
从上面过程中可以看出如果我们正常的对
2
31
2^{31}
231进行计算的话循环一共需要31次,而上述的快速幂算法只需要循环7次(除去上述啰嗦的一些算式)即可以解决。可以看出快速幂算法是比普通的循环要快。那么下面给出快速幂算法最原始的代码:
long long fast_pow(long long a, long long b)
{
long long ans = 1;
while(b != 0) // 判断指数部分是否为0
{
if(b % 2 == 0) // 若指数是偶数,底数翻倍,指数除以2
{
a = a * a;
b /= 2;
}
else // 指数部分为奇数的情况,分离一个a出去,指数部分减1
{
ans = ans * a;
b = b - 1;
}
}
return ans;
}
对于上述判断 b%2!=0 \colorbox{lavender}{b\%2!=0} b%2!=0 以及 b/=2 \colorbox{lavender}{b/=2} b/=2的时候,我们可以使用位运算来操作。 b%2 \colorbox{lavender}{b\%2} b%2的操作和 b&1 \colorbox{lavender}{b\&1} b&1的操作是等效的,而 b/=2 \colorbox{lavender}{b/=2} b/=2的操作和 b>>1 \colorbox{lavender}{b>>1} b>>1的操作也是等效的。另外当 b \colorbox{lavender}b b为奇数的时候, b-1 \colorbox{lavender}{b-1} b-1其实等价于 b>>1 \colorbox{lavender}{b>>1} b>>1,向右位移一位去掉二进制中最后一位的1。位运算可以加快运算速度,所以上述代码也可以改成(知道过程就可以自己写出来,自己能够写出来就行):
long long fast_pow(long long a, long long b)
{
long long ans = 1;
while(b) // 判断指数部分是否为0
{
if(b & 1) // 指数部分是否为奇数
{
ans *= a;
b >>= 1;
}
else
{
a *= a;
b >>= 1;
}
}
return ans;
}
这里面发现可以合并的代码现在合并在一起如下:
long long fast_pow(long long a, long long b)
{
long long ans = 1;
while(b)
{
if(b & 1) ans *= a;
else a *= a;
b >>= 1;
}
return ans;
}
在这里指数奇数的情况可以和偶数的情况同时计算,代码如下(最好记住,记不住的话尽量把过程记住):
long long fast_pow(long long a, long long b)
{
long long ans = 1;
while(b)
{
if(b & 1) ans *= a; // 指数是否为奇数,奇数执行,执行完之后立即执行偶数的情况
a *= a;
b >>= 1;
}
return ans;
}
当然这里也有一个for循环的版本:
long long fast_pow(long long a, long long b)
{
long long ans = 1;
for(;b;b>>=1)
{
if(b & 1) ans *= a;
a *= a;
}
return ans;
}
对于这道题来说还有一个问题,就是求出题的答案。假设给定两个数,一个为
a
a
a,另一个数为
b
b
b,给定
a
a
a和
b
b
b的表达式如下:
a
=
m
p
+
i
b
=
n
p
+
j
\begin{aligned} &a=mp+i \\ &b = np + j \end{aligned}
a=mp+ib=np+j
式子中的
m
,
n
,
i
,
j
m,n,i,j
m,n,i,j均为整数,且满足
0
≤
i
,
j
≤
p
0 \le i,j \le p
0≤i,j≤p,求
a
b
%
p
ab\%p
ab%p的结果。现在进行一个推导:
a
b
=
(
m
p
+
i
)
(
n
p
+
j
)
=
[
m
n
p
+
m
j
+
n
i
]
p
+
i
j
\begin{aligned}ab &=(mp+i)(np+j)\\&= [mnp + mj + ni]p + ij \end{aligned}
ab=(mp+i)(np+j)=[mnp+mj+ni]p+ij
对于
a
b
=
(
m
p
+
i
)
(
n
p
+
j
)
ab =(mp+i)(np+j)
ab=(mp+i)(np+j)来说,肯定能够被
p
p
p整除。而对于
i
j
ij
ij来说,若
0
≤
i
j
<
p
0 \le ij < p
0≤ij<p,则余数为
i
j
ij
ij,若
i
j
≥
p
ij \ge p
ij≥p,则余数为
i
j
%
p
ij\%p
ij%p。综上所述,可以得出如下结论:
a
b
%
p
=
[
(
a
%
p
)
(
b
%
p
)
]
%
p
\begin{aligned}ab\%p=[(a\%p)(b\%p)]\%p\end{aligned}
ab%p=[(a%p)(b%p)]%p
接下来难度提高一点,求解
a
b
%
p
a^b\%p
ab%p。根据上述的条件,我们可以得到一下结论:
a
b
%
p
=
(
a
×
a
×
⋯
×
a
⏟
)
b
%
p
=
[
(
a
%
p
)
(
a
%
p
)
⋯
(
a
%
p
)
⏟
]
b
%
p
\begin{aligned}a^b\%p &= \begin{matrix}( \underbrace{a \times a \times \cdots \times a}) \\ b \end{matrix}\% p \\&= \begin{matrix}[\underbrace{(a\%p)(a\%p)\cdots(a\%p)}] \\ b\end{matrix} \% p \end{aligned}
ab%p=(
a×a×⋯×a)b%p=[
(a%p)(a%p)⋯(a%p)]b%p
若多了个系数
k
k
k,则:
(
k
×
a
b
)
=
[
(
k
%
p
)
(
a
%
p
)
(
a
%
p
)
⋯
(
a
%
p
)
⏟
]
b
%
p
\begin{aligned}(k \times a^b) = \begin{matrix}[(k \% p) \underbrace{(a\%p)(a\%p)\cdots(a\%p)}]\\ b \end{matrix} \% p\end{aligned}
(k×ab)=[(k%p)
(a%p)(a%p)⋯(a%p)]b%p
根据这个式子可以推导出快速幂模板如下:
算法模板一
\colorbox{yellow}{算法模板一}
算法模板一:
long long fast_pow(long long a, long long b, long long p)
{
long long ans = 1;
while(b)
{
if(b & 1) ans = ans * a % p; // 系数k%p
a = a * a % p; // b个a%p
b >>= 1;
}
return ans % p; // 当b直接等于0的时候循环无法解决,所以此处直接对ans取模
}
算法模板二 \colorbox{yellow}{算法模板二} 算法模板二:
long long fast_pow(long long a, long long b, long long p)
{
long long ans = 1;
for(;b;b>>=1)
{
if(b & 1) ans = ans * a % p;
a = a * a % p;
}
return ans % p;
}
上面的两个 模板的区别仅在于是用 f o r for for循环还是是用 w h i l e while while循环。这两个模板需要熟练掌握其中一个模板,以此来应对快速幂算法的题。
本题题解代码如下:
#include <stdio.h>
int main()
{
long long a, b, p, ans = 1;
scanf("%lld%lld%lld", &a, &b, &p);
for(; b; b>>=1)
{
if(b&1) ans = ans * a % p;
a = a * a % p;
}
ans %= p; // 特殊处理当b直接输入0的情况,其他情况此处ans还是ans。
printf("%lld", ans);
return 0;
}
❥(^_-)第二题 64 64 64位整型乘法 【acwing 90】
求 a 乘 b 对 p 取模的值。
输入格式: 第一行输入整数
a
\color{red}\colorbox{lavender}{a}
a,第二行输入整数
b
\color{red}\colorbox{lavender}{b}
b,第三行输入整数
c
\color{red}\colorbox{lavender}{c}
c。
输出格式:输出一个整数,表示
ab mod p
\color{red}\colorbox{lavender}{ab mod p}
ab mod p的值。
数据范围: 0 ≤ a , b , p ≤ 1 0 18 0\le a,b,p\le 10^{18} 0≤a,b,p≤1018
输入样例1:
3
2
7
\colorbox{lavender}3 \\ \colorbox{lavender}2 \\\colorbox{lavender}7
327
输出样例1:
6
\colorbox{lavender}{6}
6
输入样例2:
5201314
520
1314
\colorbox{lavender}{5201314}\\\colorbox{lavender}{520}\\\colorbox{lavender}{1314}
52013145201314
输出样例2:
868
\colorbox{lavender}{868}
868
输入样例3:
131452013145201314
520131452013145201
770880131452013920
\colorbox{lavender}{131452013145201314} \\\colorbox{lavender}{520131452013145201}\\ \colorbox{lavender}{770880131452013920}
131452013145201314520131452013145201770880131452013920
输出样例3:
101736777287696674
\colorbox{lavender}{101736777287696674}
101736777287696674
思路:
首先这道题我们可以看成是
a
a
a个
b
b
b或者是
b
b
b个
a
a
a相乘再求余数,则可以得出如下表达式:
a
b
%
p
=
(
a
×
a
×
⋯
×
a
)
⏟
b
%
p
=
[
(
a
%
p
)
(
a
%
p
)
⋯
(
a
%
p
)
⏟
]
b
%
p
\begin{aligned}ab\%p &=\begin{matrix} \underbrace{(a \times a \times \cdots \times a)} \\ b \end{matrix} \% p \\&= \begin{matrix} [\underbrace{(a \%p)(a\%p)\cdots(a\%p)}]\\b\end{matrix}\%p\end{aligned}
ab%p=
(a×a×⋯×a)b%p=[
(a%p)(a%p)⋯(a%p)]b%p
从上述推导过程中可知只需要使用循环运算 b b b次即可。可以得出如下代码:
long long mul(long long a, long long b, long long p)
{
long long ans = 1;
for(; b; b -= 1)
ans = ans * a % p;
return ans;
}
但是在前面曾经提到过一秒可以运算的次数,而此处的
b
b
b的范围是
0
≤
b
≤
1
0
18
0 \le b \le 10^{18}
0≤b≤1018,所以运算
b
b
b次一定会TLE(Time Limit Exceeded,时间超时)。所以换一个思路,根据上一个题推导出来的式子:
a
b
%
p
=
[
(
a
%
p
)
(
b
%
p
)
]
%
p
\begin{aligned}ab\%p=[(a\%p)(b\%p)]\%p\end{aligned}
ab%p=[(a%p)(b%p)]%p
此时可以得出如下计算的代码:
long long mul(long long a, long long b, long long p)
{
a %= p;
b %= p;
return (a * b) % p;
}
但是很遗憾,这个式子也不能帮我们解决这个问题。因为
0
≤
a
,
b
,
p
≤
1
0
18
0\le a,b,p\le 10^{18}
0≤a,b,p≤1018,所以此处的
(
a
%
p
)
(
b
%
p
)
(a\%p)(b\%p)
(a%p)(b%p)可能会超数据类型范围。此时我们需要考虑一个新的算法。接下来我们来进行推导。假设
b
b
b的二进制有
k
k
k位且满足如下式子:
b
=
c
k
−
1
×
2
k
−
1
+
c
k
−
2
×
2
k
−
2
+
⋯
+
c
1
×
2
1
+
c
0
×
2
0
\begin{aligned}b=c_{k-1}\times2^{k-1} + c_{k-2} \times 2 ^{k-2}+\cdots + c_1 \times 2^1 + c_0 \times 2^0\end{aligned}
b=ck−1×2k−1+ck−2×2k−2+⋯+c1×21+c0×20
则
a
b
=
c
k
−
1
×
a
×
2
k
−
1
+
c
k
−
2
×
a
×
2
k
−
2
+
⋯
+
c
1
×
a
×
2
1
+
c
0
×
a
×
2
0
\begin{aligned}ab=c_{k-1} \times a \times 2^{k-1} + c_{k-2} \times a \times 2 ^{k-2}+\cdots + c_1 \times a \times 2^1 + c_0 \times a \times 2^0\end{aligned}
ab=ck−1×a×2k−1+ck−2×a×2k−2+⋯+c1×a×21+c0×a×20
又
a
×
2
i
=
(
a
×
2
i
−
1
)
×
2
\begin{aligned}a \times 2^{i} =(a \times2^{i-1}) \times 2\end{aligned}
a×2i=(a×2i−1)×2
所以可以得出如下结论:
(
a
×
2
i
)
%
p
=
{
[
(
a
×
2
i
−
1
)
%
p
]
×
2
}
%
p
\begin{aligned}(a \times 2^i) \%p=\{[(a \times 2^{i-1})\%p] \times 2\} \%p\end{aligned}
(a×2i)%p={[(a×2i−1)%p]×2}%p
上述结论看不懂可参考
a
b
%
p
=
[
(
a
%
p
)
(
b
%
p
)
]
%
p
ab\%p=[(a\%p)(b\%p)]\%p
ab%p=[(a%p)(b%p)]%p,此处默认
b
%
p
=
2
b\%p=2
b%p=2。故可以得出来以下代码模板:
long long mul(long long a, long long b, long long p)
{
long long ans = 0;
for(; b; b >>= 1)
{
if(b&1) ans = (ans + a) % p; // b二进制最后以为是否为1,保持ans答案在数据范围中。
a = a * 2 % p; // 执行结论操作
}
return ans;
}
代码:
#include <stdio.h>
int main()
{
long long a, b, p, ans = 0;
scanf("%lld%lld%lld", &a, &b, &p);
for(; b; b >>= 1)
{
if(b&1) ans = (ans + a) % p;
a = a * 2 % p;
}
printf("%lld", ans);
return 0;
}
❥(^_-)第三题 斐波那契数列
F i b o n a c c i Fibonacci Fibonacci数列的递推公式为: F n = F n − 1 + F n − 2 F_n=F_{n-1}+F_{n-2} Fn=Fn−1+Fn−2,其 F 1 = F 2 = 1 F_1=F_2=1 F1=F2=1。当 n n n比较大, F n F_n Fn也非常大,现在我们想知道, F n F_n Fn除以 5201314 5201314 5201314的余数是多少。
输入格式:输入包含一个整数
n
\colorbox{lavender}n
n。
输出格式:输出一行,包含一个整数,表示
F
n
F_n
Fn除以
5201314
的余数
\colorbox{lavender}{5201314}的余数
5201314的余数。
数据范围:
1
≤
n
≤
5201314
1 \le n \le 5201314
1≤n≤5201314。
输入样例1:
520
\colorbox{lavender}{520}
520
输出样例1:
97115
\colorbox{lavender}{97115}
97115
输入样例2:
1314
\colorbox{lavender}{1314}
1314
输出样例2:
1136042
\colorbox{lavender}{1136042}
1136042
输入样例3:
5201314
\colorbox{lavender}{5201314}
5201314
输出样例3:
1906391
\colorbox{lavender}{1906391}
1906391
思路: 本题的思路其实和前面的两道题大同小异,由于斐波那契数列后面的数越来越大,所以可以对每一项都进行取余操作即可。
代码:
#include <stdio.h>
int main()
{
int a = 1, b = 1, n, ans = 1;
scanf("%d", &n);
for(int i = 3; i <= n; i ++)
{
ans = (a + b) % 5201314;
a = b;
b = ans;
}
printf("%d", ans);
return 0;
}