oj2011 分治 快速幂 高精度除低精度

2011

出处:http://ybt.ssoier.cn:8088/problem_show.php?pid=1234

Description

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

Input

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

Output

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

Sample Input

3
5
28
792

Sample Output

1051
81
5521

开始看到这个题准备直接快速幂,后来发现题目的输入是200位的正整数,高精度好久没碰了,看了题解,两种做法,还挺新奇,但还是有疑惑。

Source

解法一:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

char ch[202];
int a[502];       //存2011的n次方余10000的所有情况,但数组开的大小,有待探讨。
int b[202];


int main()
{
	int n = 0, temp = 2011;
	do{
		a[++n] = temp;
		temp = temp * 2011 % 10000;
		
	}while(temp != 2011);
	a[0] = a[n];        //此处赋值给a[0]与后面幂取模的值对应。
	int t, k, x, len;
	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;  
		}
		printf("%d\n", a[x]);
	}
}

关于给数组a开多大,做了一下测试:

	int t;
	scanf("%d", &t);
	for(int i = 0; i < t; i++)
	{
		long long int temp;
		scanf("%d", &temp);
		int n = 0;
		int T = temp;
		do{
			a[++n] = temp;
			temp = temp * T % 10000;
            if(temp == 0)
                break;
		}while(temp != T && n < 10000);//开始判断只有temp != T,发现有的数的不出结果
		a[0] = a[n];
		printf("%d :n = %d \n", temp, n);
	}

计算了不同底数的n次方余10000的情况数,得到底数为 2011的情况确实数为500,随便输入些数2111为250,2901为100,2012、2015、2018的情况数均大于10000,以2,4,5,8为个位上的数情况数均大于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;
		for(int j = len-4; j < len; ++j)//z为幂,这里直接取输入的幂的低四位
			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;
}

这种解法的没弄懂的地方在于直接取输入的幂的低四位来作计算,这里如果事先知道2011^n的所有情况只有500种的话,取低四位就能得到所有情况,但直接拿低四位的数作幂运算与拿原数作幂运算得到的结果能一一对应吗?不懂。

解法三:(这个中规中矩,纯粹 快速幂+高精度

参考博文:https://blog.csdn.net/mrcrack/article/details/78551001

#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;
//	while(b[0] && b[i--] == 0) //若要直接加b[0]--,条件需改进。
//		b[0]--;
//	if(!b[0])
//		b[0] = 1; 
}

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';
		while(!(b[0] == 1 && b[1] == 0))快速幂,(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;
}

这种算是快速幂换一种写法,原理一样,都是用一个变量temp不断的累成,并对当前幂的2进制判断,若第一位为1则ans= (ans*temp)%10000,同时将幂除2(将其2进制形式整体右移一位)。

既然到了涉及到了快速幂就总结下几种实现方法。

快速幂不熟的话这篇博客讲得好https://www.cnblogs.com/CXCXCXC/p/4641812.html

求a^b

一般

int power(int a, int b)
{
    int ans = 1, temp = a;
    while(b)
    {
        if(b & 1)
            ans *= temp;
        temp *= temp;
        b >> 1;
    }
    return ans;
}

分治递归思想:任何一个自然数b,有b = 2*b/2+b%2,如19 = 2 * 9 + 1, 即a^19 = a ^(2*9) * 2 = a^9 * a^9 * a,同时9可以继续拆分下去便得最终解。

int power(int a, int b)
{
    if(b == 0)
        return 1;
    int temp = power(a, b/2);
    temp = temp * temp;
    if(b%2 == 1)
        temp = temp * a;
    return temp;
}

 

 

话说今天情人节

亲爱的,
你还好吗?冷不冷?吃饭了吗?

 

你叫什么名字呢?/huaji

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页