NOI 2991:2001 高精度运算/模运算的性质/取模的规律性/分治法/快速幂

刷到一道挺好的题目,复习了很多知识点,在此整理一下
解法参考:https://blog.csdn.net/github_39329077/article/details/86708747


题目

2991:2011

总时间限制: 1000ms 内存限制: 65536kB

描述

已知长度最大为200位的正整数n,请求出2011n的后四位。

输入

第一行为一个正整数k,代表有k组数据,k<=200接下来的k行,每行都有一个正整数n,n的位数<=200

输出

每一个n的结果为一个整数占一行,若不足4位,去除高位多余的0

样例输入

3
5
28
792

样例输出

1051
81
5521

模运算的性质

之后的几个解法都要用到其中一些性质

模运算与基本四则运算有些相似,但是除法例外。其规则如下:
(a + b) % p = (a % p + b % p) % p (1)
(a - b) % p = (a % p - b % p ) % p (2)
(a * b) % p = (a % p * b % p) % p (3)
(ab) % p = ((a % p)b) % p (4)
结合律:
((a+b) % p + c) % p = (a + (b+c) % p) % p (5)
((a*b) % p * c)% p = (a * (b * c)%p) % p (6)
交换律:
(a + b) % p = (b+a) % p (7)
(a * b) % p = (b * a) % p (8)
分配律:
((a +b)% p * c) % p = ((a * c) % p + (b * c) % p) % p (9)
重要定理:
若a≡b (% p),则对于任意的c,都有(a + c) ≡ (b + c) (%p);(10)
若a≡b (% p),则对于任意的正整数c,都有(a * c) ≡ (b * c) (%p);(11)
若a≡b (% p),c≡d (% p),则 (a + c) ≡ (b + d) (%p),(a - c) ≡ (b - d) (%p),
(a * c) ≡ (b * d) (%p); (12)


解法

解法一

打表找到2011n模10000的结果的规律,发现500种一循环
同时利用模运算的性质,例子
9876 % n
= (9 * 103 + 8 * 102 + 7 * 101 + 6 * 100 )%n
= (((9 * 10 + 8) * 10 + 7) * 10 + 6)%n
=((((9%n) * 10 + 8)%n * 10 + 7)%n * 10 + 6)%n

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
 
char ch[202];  //字符串形式存n
int a[502];    //存2011的n次方余10000的所有情况,但数组开的大小,有待探讨。
int b[202];  //将n每位化成整数存下
 
 
int main()
{
	int n = 0, temp = 2011;
	//找到2011^n的后四位的所有情况的个数,退出循环时n为500
	do{
		a[++n] = temp;
		temp = temp * 2011 % 10000;
		
	}while(temp != 2011);
	a[0] = a[n];        //此处赋值给a[0]与后面幂取模的值对应。
	int t, k, x, len;  //k是之后遍历b数组时的下标
	scanf("%d", &t);
	for(int i = 0; i < t; i++)
	{
		x = 0, k = 0;
		memset(b, 0, sizeof(b));
		scanf("%s", ch);
		len = strlen(ch);
		for(int j = 0; j < len; ++j)
			b[j] = ch[j] - '0';
		while(k < len)  //n为2011^n后四位所有情况的个数,此处对高精度幂模n
			x = (x * 10 + b[k++]) % n;  //得到大数对n取余结果
		printf("%d\n", a[x]);
	}
}

解法二

这个解法直接取输入的幂的低四位来作计算,为什么呢?
编个循环代码跑了一下发现201110000%10000=1
我们可以令 n = k * 10000+r(k,r都是整数),那么我们想要求的2011n的后四位可以表示为
(2011n)%10000
= (2011k*10000+r )%10000
= (2011k*10000 * 2011r)%10000
= (201110000 % 10000)k * (2011r %10000)
= 1 * 2011r %10000
= 2011r %10000

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
 
char ch[202];
int main()
{
	int t;
	scanf("%d", &t);
	for(int i = 0; i < t; ++i)
	{
		scanf("%s", ch);
		int len = strlen(ch);
		int z= 0;
		//z为幂,这里直接取输入的幂的低四位
		for(int j = len-4; j < len; ++j)
			if(j >= 0)
				z = z*10 + ch[j] - '0';
		int p = 1, temp = 2011;  
// 类似快速幂,但是是先算完小于z的最大2^k部分然后再枚举计算后面部分(如z == 18, 先算到2011的2^4幂即2011^16, 后面再逐个*2011至2011^18)
		while(p * 2 <= z)
		{
			temp = temp * temp % 10000;
			p *= 2;
		}
		for(int i = p+1; i <= z; ++i)
			temp = temp * 2011 % 10000;
		printf("%d\n", temp);
//		int temp = 2011 % 10000, ans = 1; // 快速幂写法
//		while(z)
//		{
//			if(z & 1)
//				ans = ans * temp % 10000;
//			temp = temp * temp % 10000;
//			z >>= 1;
//		}
//		printf("%d\n", ans);	
	}
	return 0;
}

解法三 快速幂+高精度

这个最基础,但是也适合最一般的情况

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char ch[202];
int b[202];
/*
高精度除低精度
*/
void devide()
{
	int i = b[0];
	while(i)
	{
		if(i > 1)
			b[i-1] += (b[i]%2)*10; 
		b[i] /= 2;
		i--;
	}
	i = b[0];
	while(b[i] == 0)//最初while循环里加了b[0]--;导致出不了循环。 
		i--;
	//注意while条件里加i--,最后一次判断无论是否为1,i均减1。 
	if(i == 0)
		b[0] = 1;
	else
		b[0] = i;
}
 
int main()
{
	int t;
	scanf("%d", &t);
	for(int i = 0; i < t; i++)
	{
		int temp = 2011, ans = 1;
		scanf("%s", ch);
		b[0] = strlen(ch);
		for(int i = 1; i <= b[0]; i++)
			b[i] = ch[b[0]-i] - '0';
		
		//快速幂,(b[0] == 1 && b[1] == 0)均满足时表示幂为零。
		while(!(b[0] == 1 && b[1] == 0))
		{
			if(b[1]%2 == 1)
				ans = (ans * temp) % 10000;
			temp = (temp * temp) % 10000;
			devide();
		}
		printf("%d\n", ans);
	}
	return 0;
}

快速幂模板

求 ab ,返回值类型根据题目来,有时可用int ,有时用int就不够,需要long long

讲解可看https://www.cnblogs.com/CXCXCXC/p/4641812.html

版本一:位运算

11的二进制是1101(b),我们将 a 11 a^{11} a11转化为算 a 2 0 ∗ a 2 1 ∗ a 2 3 a^{2^0}*a^{2^1}*a^{2^3} a20a21a23 ,也就是 a 1 ∗ a 2 ∗ a 8 a^1 * a^2 * a^8 a1a2a8

long long power(int a, int b){
	long long res = 1, base = a;
	while(b>0){
		if(b&1)  //若当前b的最低位为1
			res *= base;
		base *= base;
		b >>= 1;
	}
	return res;
}
版本二:分治法

任意一个自然数n,可以n = 2 * n/2 + n%2,如19 = 2 * 9 + 1,
即a19 = a2*9+1 = (a9)2 * a,同时9可以继续拆分下去便得最终解。

int power(int a, int b){
	if(b==0) return 1;
	if(b==1) return a;
	int res = power(a,b/2);
	res *= res;
	return res * power(a,b%2);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值