信息学奥赛一本通 1234:2011 | OpenJudge NOI 2.4 2991:2011

【题目链接】

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 20112011500%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=2011500n/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 2011500n/500+n%500=(2011500)n/500%100002011n%500%10000=(2011500%10000)n/5002011n%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;
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值