C语言程序设计100例之(41):快速幂运算
例41 快速幂运算
题目描述
输入三个整数 b,p,k(0≤b,p,k<231),求 b^p mod k
输入格式
一行三个整数 b,p,k
输出格式
输出 b^p mod k=s (s 为运算结果)
输入样例
2 10 9
输出样例
2^10 mod 9=7
(1)编程思路。
在实际应用中,我们经常会用到幂运算,例如,an为a的n次幂。求a的n次方通常采用快速幂运算。下面我们来探讨快速幂运算的思路。
由于乘法具有结合律,因此 a4 = a*a * a *a = (a*a) * (a*a) = a2 * a2。由此可以得到这样的结论:当n为偶数时,an = a n/2 * a n/2;当n为奇数时,an = a n/2 * a n/2 * a (其中n/2取整)。这样,我们可以采用一种类似于二分的思想快速求得a的n次幂。
例如,a9 =a*a*a*a*a*a*a*a*a (一个一个乘,要乘9次)
=a*(a*a)*(a*a)*(a*a)*(a*a)
=a*(a2)4
= a*((a2)2)2 (A平方后,再平方,再平方,再乘上剩下的一个a,只乘4次)
(2)源程序。
#include
typedef long long LL;
LL quickPower(LL a, LL b,LL k)
{
LL p = 1;
while (b)
{
if (b&1) p = (p*a)% k;
b >>= 1;
a = (a%k*a%k)%k;
}
return p%k;
}
int main()
{
LL a,b,k;
scanf("%lld%lld%lld",&a,&b,&k);
printf("%lld^%lld mod %lld=%lld\n",a,b,k,quickPower(a,b,k));
return 0;
}
习题41
41-1 组合数
本题选自洛谷题库 (https://www.luogu.org/problem/P3414)
题目描述
小明刚学了组合数。现在他很想知道sigma(C(n,i))是多少;其中C是组合数(即C(n,i)表示n个物品无顺序选取i个的方案数),i取从0到n所有偶数。
由于答案可能很大,请输出答案对6662333的余数。
输入格式
输入仅包含一个整数n。
输出格式
输出一个整数,即为答案。
输入样例
输出样例
(1)编程思路。
由二项式定理可知,
因此本题实质是求输入整数n的2的(n-1)次幂对6662333的余数。
由于n较大,采用快速幂运算完成。
(2)源程序。
#include
#define MODNUM 6662333
typedef long long LL;
LL quickPower(LL a, LL b)
{
LL p = 1;
while (b)
{
if (b&1) p = p*a%MODNUM;
b >>= 1;
a = a*a%MODNUM;
}
return p;
}
int main()
{
LL n;
scanf("%lld", &n);
printf("%lld\n", quickPower(2, n-1));
return 0;
}
41-2 汉诺塔V
本题选自杭州电子科技大学OJ题库 (http://acm.hdu.edu.cn/showproblem.php?pid=1995)
Problem Description
用1,2,...,n表示n个盘子,称为1号盘,2号盘,...。号数大盘子就大。经典的汉诺塔问题经常作为一个递归的经典例题存在。可能有人并不知道汉诺塔问题的典故。汉诺塔来源于印度传说的一个故事,上帝创造世界时作了三根金刚石柱子,在一根柱子上从下往上按大小顺序摞着64片黄金圆盘。上帝命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一回只能移动一个圆盘。我们知道最少需要移动2^64-1次.在移动过程中发现,有的圆盘移动次数多,有的少 。告之盘子总数和盘号,计算该盘子的移动次数.
Input
包含多组数据,首先输入T,表示有T组数据.每个数据一行,是盘子的数目N(1<=N<=60)和盘
号k(1<=k<=N)。
Output
对于每组数据,输出一个数,到达目标时k号盘需要的最少移动数。
Sample Input
60 1
3 1
Sample Output
576460752303423488
(1)编程思路。
一般我们在学习程序设计时,会采用递归来解汉诺塔问题,N个圆盘共需要移动2N-1次。为求得N个圆盘中盘号为k的圆盘移动次数,我们通过列举找出规律。
1个盘 1号:1次
2个盘 1号:2次 2号:1次
3个盘 1号:4次 2号:2次 3号:1次
4个盘 1号:8次 2号:4次 3号:2次 4号:1次
……
N个盘 1号:2N-1次 2号:2N-2次 ...k号:2N-k次 ...N号:1次
因此,本题实质求2N-k,采用快速幂运算完成。
(2)源程序。
#include
typedef long long LL;
LL quickPower(LL a, LL b)
{
LL p = 1;
while (b)
{
if (b&1) p = p*a;
b >>= 1;
a = a*a;
}
return p;
}
int main()
{
int t;
scanf("%d",&t);
while (t--)
{
LL n,k;
scanf("%lld%lld",&n,&k);
printf("%lld\n",quickPower(2,n-k));
}
return 0;
}
41-3 Pseudoprime numbers
本题选自北京大学OJ题库(http://poj.org/problem?id=3641)。
Description
Fermat's theorem states that for any prime number p and for any integer a > 1, ap = a (mod p). That is, if we raise a to the pth power and divide by p, the remainder is a. Some (but not very many) non-prime values of p, known as base-a pseudoprimes, have this property for some a. (And some, known as Carmichael Numbers, are base-a pseudoprimes for all a.)
Given 2 < p ≤ 1000000000 and 1 < a < p, determine whether or not p is a base-a pseudoprime.
Input
Input contains several test cases followed by a line containing "0 0". Each test case consists of a line containing p and a.
Output
For each test case, output "yes" if p is a base-a pseudoprime; otherwise output "no".
Sample Input
3 2
10 3
341 2
341 3
1105 2
1105 3
0 0
Sample Output
no
no
yes
no
yes
yes
(1)编程思路。
题目是判断输入的p是不是伪素数。伪素数条件:①p不是素数。② ap = a (mod p)。
第①个条件编写函数int isPrime(LL x),判断整数x是否是素数,若是返回值1,否则返回值0;第②个条件进行快速幂运算。
(2)源程序。
#include
typedef long long LL;
int isPrime(LL x)
{
for (LL i=2;i*i<=x;i++)
if (x%i==0)
return 0;
return 1;
}
LL quickPower(LL a, LL b,LL k)
{
LL p = 1;
while (b)
{
if (b&1) p = p*a%k;
b >>= 1;
a = a%k*a%k;
}
return p%k;
}
int main()
{
LL p,a;
while (scanf("%lld%lld",&p,&a))
{
if (a==0&&p==0) break;
if (isPrime(p)==1)
printf("no\n");
else
{
if(quickPower(a,p,p)==a%p)
printf("yes\n");
else
printf("no\n");
}
}
return 0;
}