【题目链接】
ybt 1234:2011
OpenJudge NOI 2.4 2991:2011
【题目考点】
1. 快速幂
2. 高精度
【解题思路】
解法1:用高精度数字表示指数
n有200位,只能用高精度数字表示。求乘方,自然要使用快速幂。
考察低精度运算快速幂的基本代码:
int Pow(int a, int b)//快速幂求a^b
{
int r = 1;
while(b > 0)
{
if(b % 2 == 1)
r *= a;
a *= a;
b /= 2;
}
return r;
}
与指数b相关的运算有:
- 判断高精度数字是否大于0:数字位数大于1,或数字位数为1但个位数字大于0,即为该数字大于0。
- 判断高精度数字是否是奇数:如果个位数字是奇数,则该数字为奇数。
- 高精度数字除以低精度数字
实现以上高精度运算后,将快速幂函数改写为指数为高精度数字的快速幂函数。为了取后四位,每步运算后对结果取模10000。
解法2:利用 201 1 500 % 10000 = 1 2011^{500}\%10000 = 1 2011500%10000=1
借助该题中的概念:信息学奥赛一本通 1933:【05NOIP普及组】循环 | 洛谷 P1050 [NOIP2005 普及组] 循环
循环长度:如果n的循环长度是L,那么说明对于任意的正整数a,
n
a
n^a
na和
n
a
+
L
n^{a+L}
na+L的最后k位都相同。
本题,我们要确定2011的循环长度。如果理解了上题中求循环长度的方法,并写出了代码,我们可以运行【信息学奥赛一本通 1933:【05NOIP普及组】循环 】这一题的题解代码,输入2011 4,得到输出为500,这就是2011的循环长度。
也可以用同样的算法,手动求解确认。
由于每次要做的事情是一样的,可以写一个小程序来辅助寻找循环长度。
#include <bits/stdc++.h>
using namespace std;
int main()
{//看a的几次方模m的结果等于a,这就是循环长度。
int a, m, s, i;
cin >> a >> m;
s = a;
for(i = 1; i <= 10; ++i)
{
cout << s << '*' << a << '%' << m << '=' << s * a % m << endl;
s = s * a % m;
if(s == a)
break;
}
cout << "len=" << i << endl;
return 0;
}
2011末1位的循环长度:
输入1 10
1*1%10=1
len=1
2011末2位的循环长度:
输入11 100
11*11%100=21
21*11%100=31
31*11%100=41
41*11%100=51
51*11%100=61
61*11%100=71
71*11%100=81
81*11%100=91
91*11%100=1
1*11%100=11
len=10
2011末3位的循环长度:
已知 1 1 10 % 1000 = 601 11^{10}\%1000=601 1110%1000=601
输入:601 1000
601*601%1000=201
201*601%1000=801
801*601%1000=401
401*601%1000=1
1*601%1000=601
len=5
由于每次乘的是 1 1 10 11^{10} 1110,末三位循环长度实际为50
2011末4位的循环长度:
已知 201 1 50 % 10000 = 3001 2011^{50}\%10000=3001 201150%10000=3001(可以使用1326:【例7.5】 取余运算(mod)中的程序求这个值)
输入3001 10000
3001*3001%10000=6001
6001*3001%10000=9001
9001*3001%10000=2001
2001*3001%10000=5001
5001*3001%10000=8001
8001*3001%10000=1001
1001*3001%10000=4001
4001*3001%10000=7001
7001*3001%10000=1
1*3001%10000=3001
len=10
由于每次乘的是 201 1 50 2011^{50} 201150,末三位循环长度实际为500
当前我们知道了
2011
∗
201
1
500
%
10000
=
2011
2011*2011^{500}\%10000=2011
2011∗2011500%10000=2011,即
201
1
500
%
10000
=
1
2011^{500}\%10000=1
2011500%10000=1,本题要求的是
201
1
n
%
10000
2011^n\%10000
2011n%10000
201
1
n
%
10000
=
201
1
500
∗
n
/
500
+
n
%
500
%
10000
2011^n\%10000=2011^{500*n/500+n\%500}\%10000
2011n%10000=2011500∗n/500+n%500%10000,其中
n
/
500
n/500
n/500是整除运算。
201
1
500
∗
n
/
500
+
n
%
500
=
(
201
1
500
)
n
/
500
%
10000
⋅
201
1
n
%
500
%
10000
=
(
201
1
500
%
10000
)
n
/
500
⋅
201
1
n
%
500
%
10000
2011^{500*n/500+n\%500}=(2011^{500})^{n/500}\%10000\cdot 2011^{n\%500}\%10000=(2011^{500}\%10000)^{n/500}\cdot 2011^{n\%500}\%10000
2011500∗n/500+n%500=(2011500)n/500%10000⋅2011n%500%10000=(2011500%10000)n/500⋅2011n%500%10000,而已知
201
1
500
%
10000
=
1
2011^{500}\%10000=1
2011500%10000=1,那么前一项为1,所以
201
1
n
%
10000
=
201
1
n
%
500
%
10000
2011^n\%10000 = 2011^{n\%500}\%10000
2011n%10000=2011n%500%10000,即为将指数n模500后,求2011的n次方模10000的值。
输入表示指数的字符串,只取末3位构成一个整数n(要考虑到原数字不足3位的情况),再对这个整数n取模500,这就是处理后的n。接着求
201
1
n
%
10000
2011^n\%10000
2011n%10000即可。由于此时的n是小于500的数字,这里都不需要使用快速幂,直接循环求幂即可。
【题解代码】
解法1:用高精度数字表示指数
#include<bits/stdc++.h>
using namespace std;
#define N 205
#define M 10000
void DivideBy(int a[], int b)//高精除低精 a /= b
{
int x = 0, i;
for(i = a[0]; i >= 1; i--)
{
x = x * 10 + a[i];
a[i] = x / b;
x = x % b;
}
i = a[0];
while(a[i] == 0 && i > 1)
i--;
a[0] = i;
}
bool isLargerThan0(int a[])//判断高精度数字a是否大于0
{
return a[0] > 1 || a[0] == 1 && a[1] > 0;
}
bool isOdd(int a[])//判断高精度数字a是否是奇数
{
return a[1] % 2 == 1;
}
int fastPowModk(int a, int b[])//指数为高精度数字的快速幂
{
int ans = 1;
while(isLargerThan0(b))
{
if(isOdd(b))
ans = ans * a % M;//每次运算的结果取模10000
a = a * a % M;//底数也取模10000
DivideBy(b, 2);
}
return ans;
}
void toNum(char s[], int a[])//字符数组转为数字数组
{
a[0] = strlen(s);
for(int i = 1; i <= a[0]; ++i)
a[i] = s[a[0] - i] - '0';
}
int main()
{
int k, n[N];//n:高精度数字,表示指数
cin >> k;
char s[N];
for(int i = 1; i <= k; ++i)
{
memset(n, 0, sizeof(n));//多组数据问题,注意清空变量
cin >> s;
toNum(s, n);
cout << fastPowModk(2011, n) << endl;
}
return 0;
}
解法2:利用 201 1 500 % 10000 = 1 2011^{500}\%10000 = 1 2011500%10000=1
#include<bits/stdc++.h>
using namespace std;
#define N 205
#define M 10000
int main()
{
int k, len, n, ans;
char s[N];
cin >> k;
for(int i = 1; i <= k; ++i)
{
cin >> s;
len = strlen(s);
n = 0;
for(int j = len - 3; j <= len - 1; ++j)//取末3位
if(j >= 0)//考虑到不足3位的情况 j可能为负数,排除这种情况
n = n * 10 + s[j] - '0';
n %= 500;
ans = 1;
for(int j = 1; j <= n; ++j)//n一定小于500,直接循环求幂
ans = ans * 2011 % M;
cout << ans << endl;
}
return 0;
}