快速幂+高精乘(填坑)洛谷1226+1045

引言

最近在刷题的时候偶然见到这样一个题目,见下图

 大致的意思是,让我们计算a的b次方取模p的结果,再我了解了关于快速幂的内容之后,很快便解决了这道题,每次乘完a后取模最后就可以得到结果。但是在这之后,我又碰到了这样一道题,见下图

这个魔性的题目竟然让我求后五百位?这种题用C语言所给的基础内置类型就行不通了 ,可能聪明的你会让人想到高精度,高精+和-我在之前博客中已经讲过,这道题是要求掌握高精度的乘法才能进一步将此题求解,我在求解完这道题后,便想将之前遗留的坑填上,便写了此篇博客。

快速幂

但是看完题先别急,想让我们看看什么是快速幂。

1.a的平方是a^2,a^2的平方是a^4,以此类推a^8,a^16,a^32。。。

2.a^x * a^y = a^(x+y) 这个也好理解

3.如果我们将b(幂的大小)写成二进制呢?

  假设b⑩ = 13  那么b② = 1101

  由此我们便可以将a^13,转化成a^8 * a^4 * a^0,而所乘的位数刚好与二进制的一相对应

  也就是说,我们可以先自增一个幂次方,每当b出现一位等于1时,向结果乘上一个事先自      增好的幂次方 当遍历完所有b二进制中1的位,便能得到最终的结果。

如果上面的话还没理解,可以结合下面的代码辅助理解,大体的意思就是,不需要一个一个往上乘a,根据幂的二进制直接乘a的更高幂次,减少计算。

while (b > 0) {//代码中用到了&(按位与)和>>(移位符)
	if (b & 1) {
//&的作用是比对两个数二进制的每一位,同为1为1,存在0则0
   //b = 1 1 0 1
   //1 = 0 0 0 1
//result 0 0 0 1
		ans = ans * a;//当b&1为真时乘上事先准备的幂次方a,存入结果中
	}
	a = a * a;
	b = b >> 1;
//将二进制位同时向右移动一位,高位补符号位
   //b = 1 1 0 1
//b>>1 = 0 1 1 0
//高位补符号位,详情可以去专门看一下我之前的博客或者别人的讲解
//b = b>>1 将结果赋给b
}

这样算下来,最终存在ans的结果便是a的b次方了

总的来说,如果 b 在二进制上的某一位是 1,我们就把答案乘上对应的 a^2^n。如果还是不太理解可以看看下面的代码实现,也是第一道题的答案

#include<stdio.h>
int main()
{
	long long int a, b, p, ans = 1;
	scanf("%lld%lld%lld", &a, &b, &p);
	long long int ar = a, br = b;
	while (b > 0) {
		if (b & 1) {
			ans = ans * a % p;//每次注意取模防止数字过大内置类型存不下
		}
		a = a * a % p;
		b = b >> 1;
	}
	ans = ans % p;
	printf("%lld^%lld mod %lld=%lld", ar, br, p, ans);
	return 0;
}

看完代码后,是不是就清晰多了?

但我们要考虑一件事,指数的增长速度那么快,用内置类型肯定,放不下,我们用高精度如何?

高精度乘

在了解这种乘法之前,我们可以回忆一下咱们小学的时候是怎样学习乘法运算的。

举个例子——12*19

 怎么理解呢?我们可以在这个式子中找找规律,比如右边个位的2和9都是在第0位(由于将数字放入数组中时是从第0位放起),它们相乘也是从第0位开始,19的1和12的2一个是第1位一个是第0位,它们相乘的结果便从第一位开始,而19的1和12的1两个都是第1位,它们在算式计算相加的时候便从第2位也就是百位开始。以上反应的规律总结以下就是

 for (int q = 0; q < la; q++) {
        for (int p = 0; p < lb; p++) {
            sum[q + p] += a[q] * b[p];
        }
}
 for(int q=0;q<la>lb?la + 3:lb + 3;q++){
    sum[q + p + 1] += sum[q + p] / 10;
    sum[q + p] %= 10;
}

在运用高精乘的过程中,也需要注意关于输入过大放不下的问题,这时候就需要用到字符串的处理,可以看看下方我专门写的一份用于高精乘的代码。

#include <stdio.h>
#include <string.h>
int main()
{
    char a0[5008];
    char b0[5008];
    int a[5008] = { 0 };
    int b[5008] = { 0 };
    int sum[5008] = { 0 };
    int i = 0;
    scanf("%s", a0);
    scanf("%s", b0);
    int v = 0;
    for (v = 5001; a0[v] != '\0'; v--);
    v--;
    for (int c = 0; v >= 0; c++, v--) {
        a[c] = a0[v] - '0';
    }
    for (v = 5002; b0[v] != '\0'; v--);
    v--;
    for (int c = 0; v >= 0; c++, v--) {
        b[c] = b0[v] - '0';
    }
    //以上代码将字符串转为数字放入数组
    int la = strlen(a0);
    int lb = strlen(b0);
    int lc = la + lb;
    for (int q = 0; q < la; q++) {
        for (int p = 0; p < lb; p++) {
            sum[q + p] += a[q] * b[p];
        }
    }
    for(int q=0;q<la>lb?la + 3:lb + 3;q++){
        sum[q + p + 1] += sum[q + p] / 10;
        sum[q + p] %= 10;
    }
    //以上代码进行高精乘
    int j = 0;
    for (j = 5005; sum[j] == 0; j--);
    for (; j >= 0; j--)
        printf("%d", sum[j]);
    //以上代码打印结果
    return 0;
}

当快速幂遇见高精乘 

这时候再将开始那道题搬运下来看看

这道题说白了最后要求求后500位其实是之前两者知识点的结合体,不过先讲讲第一问,问你位数,由于2^P-1的结果与2^P是相同的,所以可以列方程

 

 m刚刚好就是最终位数,在math.h中有计算lg的函数我们直接调用即可。

最终代码放到下面,配上注释

#include<stdio.h>
#include<math.h>
int arr[505];//充当计算2的幂次方的一个过程
int sign[505];//存放2的幂次方
int res[505];//充当最终结果的一个过程
int now[505];//存放最终结果
int main()
{
	int P;
	int ws; int i, j;
	scanf("%d", &P);
	ws = P * log10(2) + 1;
	sign[0] = 2;
	res[0] = 0;
	now[0] = 1;
	while (P > 0) {
		if (P & 1) {//当P&1为真,结果乘上2的幂次方
			for (i = 0; i <= 500; i++) {
				for (j = 0; j <= 500; j++) {
					if (i + j <= 500)//防止计算越界
						res[i + j] += now[i] * sign[j];
				}
			}
			for (i = 0; i <= 500; i++) {
				if (res[i] > 9) {
					res[i + 1] += res[i] / 10;
					res[i] %= 10;
				}
			}
			for (i = 0; i <= 500; i++) {
				now[i] = res[i];
				res[i] = 0;//用完res后要清零
			}
		}
        //下面是2的幂次方的自乘和自增
		for (i = 0; i <= 500; i++) {
			for (j = 0; j <= 500; j++) {
				if (i + j <= 500)//防止计算越界
					arr[i + j] += sign[i] * sign[j];
			}
		}
		for (i = 0; i <= 500; i++) {
			if (arr[i] > 9) {
				arr[i + 1] += arr[i] / 10;
				arr[i] %= 10;
			}
		}
		for (i = 0; i <= 500; i++) {
			sign[i] = arr[i];
			arr[i] = 0;//用完arr计算2的幂次方后清零
		}
		P >>= 1;//二进制P右移一位
	}
	now[0]--;//最终结果减一
	printf("%d\n", ws);
	for (i = 499,j=0; i >= 0; i--) {//华丽打印结果
		printf("%d", now[i]);
		j++;
		if (j >= 50) {
			printf("\n");
			j = 0;
		}
	}
	return 0;
}

结语

以上便是本次快速幂以及高精度乘法的所有内容,如果感觉对你有帮助的话,还请点个小小的赞,留下关注收藏再走啊!比心-----♥ 

  • 36
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Forcible Bug Maker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值