第三章 循环结构练习


❥(^_-)第一题 a ∧ b a\land b ab 【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}  ˆb mod p的值。

数据范围: 0 ≤ a , b ≤ 1 0 9 0\le a,b\le 10^9 0a,b109 ; 1 ≤ p ≤ 1 0 9 1 \le p \le 10^9 1p109

输入样例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×ab1的形式=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 0i,jp,求 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 0ij<p,则余数为 i j ij ij,若 i j ≥ p ij \ge p ijp,则余数为 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} 0a,b,p1018

输入样例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} 0b1018,所以运算 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} 0a,b,p1018,所以此处的 ( 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=ck1×2k1+ck2×2k2++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=ck1×a×2k1+ck2×a×2k2++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×2i1)×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×2i1)%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=Fn1+Fn2,其 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 1n5201314

输入样例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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值