快速幂:求A^B的最后三位数表示的整数。说明:AB的含义是“A的B次方”。

该博客介绍了如何使用快速幂算法在不存储庞大中间结果的情况下,求解大数乘法的最后三位数。通过不断对1000取模,避免了大数运算的存储问题,并给出了C++实现的代码示例,适用于处理大规模数据的快速计算需求。

题目描述:
求A^B的最后三位数表示的整数。说明:AB的含义是“A的B次方”。

输入:
输入数据包含多个测试实例,每个实例占一行,由两个正整数A和B组成(1<=A,B<= 10000如果A=0,B=0,则表示输入数据的结束,不做处理。

输出:
对于每个测试实例,请输出A*B的最后三位表示的整数,每个输出占一行。

样例输入:
2 3
12 6
6789 10000
0 0

样例输出:
8
984
1

分析:
只需将该数对1000 取模即可。在本例中,是否可以先求得A的B次方的具体数字后再求其后三位数呢?毫无疑问,这是不可行的。按照题面给出的输入规模,A的B次方至多可以达到10000的10000次方,这么庞大的数字是不容易存储的,但A’B的后三位数其实只与A的后三位数和B有关。由于题目要求的只是结果的后三位数,因此在计算该结果的过程中产生的中间值也只需保存其后三位数即可。即在利用快速幂求A^B的计算过程中,只需不断地将中间结果对1000取模。这样,就可以既不用担心数字不能被保存,又能够获得正确的最终结果。
在这里插入图片描述

#include<stdio.h>
#include<iostream>
using namespace std;

int AB(int a, int b){
	int pow = 1;
	while(b != 0){
		if(b % 2 == 1){
			pow *= a;
			pow %= 1000;
		}
		b /= 2;
		a *= a;
		a %= 1000;
	}
	return pow ;
}

int main(){
	int a, b ;
	while(cin >> a >> b){
		if(a == 0 && b == 0)
			break;
		else{
			a = a % 1000;
			b = b % 1000;
			cout << AB(a, b) << endl;
		}
	}
	system("pause");
	return 0;
}
<think> 我们首先需要理解题目要:将正整数用2的幂次方表示,并按约定方次表示法转换。 约定方次表示法:例如137=2^7+2^3+2^0,然后表示为2(7)+2(3)+2(0),而7=2^2+2+2^0(即7=2^2+2^1+2^0,但注意2^1不能写成2(1)?),实际上题目要递归表示:指数大于2时,需要将指数也用同样的方法表示。 具体规则: 137 = 2^7 + 2^3 + 2^0 -> "2(7)+2(3)+2(0)",但是7=2^2+2+2^0(这里2就是2^1,但不能写成2(1)),所以7的表示应为"2(2)+2+2(0)",而2(2)中的2指数为2,而2本身已经是2^1,所以直接写2,但是指数2需要递归表示为2(1)(然而题目约定:2直接用2表示,2(2)表示2^2,而指数为1时省略指数部分,指数为0时表示为0)。 实际上,题目要表示法类似于“NOIP1998复赛 普及组 幂次方”问题,规则如下: 1. 2^0表示为:2(0) 2. 2^1表示为:2 3. 2^2表示为:2(2) 4. 对于指数大于2的情况,如果指数可以继续分解,则将其分解(递归表示),如:2(2(2))表示2的(2的平方)次方,即2^4,但注意4=2^2,所以表示为2(2(2)),而3=2+2(0)(即2^1+2^0),所以2^3表示为2(2+2(0))。 因此,137的表示过程: 137 = 128 + 8 + 1 = 2^7 + 2^3 + 2^0 7 = 4+2+1 = 2^2+2^1+2^0 -> 2(2)+2+2(0) (注意:2^1直接写成2,所以整个7的表示就是2(2)+2+2(0)) 3 = 2+1 = 2 + 2(0) -> 2+2(0) 所以137表示为:2(2(2)+2+2(0))+2(2+2(0))+2(0) 但是,我们观察另一种表示:137=128+8+1,所以表达式为:2(7)+2(3)+2(0),然后将7和3分别用他们的2的幂次方表示。 注意:在表示中,加号连接,如果指数部分不是单个数字(即需要递归时),就需要把指数部分用括号括起来。 所以,我们设计一个递归函数,输入一个正整数n,返回它的约定方次表示字符串。 递归函数思路: 1. 如果n=0,则返回"0"(但注意,0次幂在表达式中是2(0),但单独0次幂不会出现?因为分解的时候已经分解到0了,但是当n=0时,我们可以返回空?实际上,当n=0时,不会调用,因为分解到0就是最后一个) 2. 如果n=1,则返回"2(0)"(因为2^0=1) 3. 如果n=2,则返回"2"(因为2^1,指数1省略) 4. 对于其他情况,我们分解n的二进制形式(实际上就是分解为2的幂次和),然后对每个幂次进行判断: 例如,n=5 -> 4+1 -> 2^2+2^0 -> 所以要表示为"2(2)+2(0)" 但是指数2大于2,所以指数2需要递归表示:2(2)中的2可以递归,但是2递归结果是"2",所以2(2)变成2(2)?不对,指数2的递归表示应该是2(1)吗?不对,根据规则,2^2的指数2大于1,且不等于2(这里规则是:指数为2时,直接写2(2)?)我们重新看规则: 规则规定:指数要用同样的方法表示,但是如果是2(即指数为2)则直接写2,而指数为1则省略。因此,对于指数2,我们递归调用时,输入2,返回"2",所以2^2的表示应该是2(2)?不对,应该是2(2)的指数部分是2,而2递归得到"2",所以整个就是2(2)?但是题目要指数2的表示是2(即指数部分直接写2)?实际上,我们看例子:137的表示中,7的分解部分,2^2被表示为2(2),即指数2没有进一步递归(因为2本身是基本单位,不需要再分解?) 重新阅读规则:137的表示中,7被表示为2(2)+2+2(0),这里2(2)表示2的平方,而2表示2的1次方,2(0)表示2的0次方。所以规则是: - 指数为0:表示为0 - 指数为1:不写指数(即只写2) - 指数为2:直接写2(即2(2)) - 指数大于2:则递归将指数表示出来,并用括号括起来 然而,在137的表示中,7的指数2被写成了2(即2(2)),而2的指数是2,为什么2(2)中的指数2不用递归?因为规则规定当指数为2时,就直接写2(在指数部分)。所以,递归函数在处理指数部分时,对于指数为2的情况,直接返回字符串"2",而不是继续递归?不对,实际上,我们在递归函数中,对于输入的数字n,我们分解它的二进制,然后对于每一个指数(即二进制中为1的位对应的指数)进行递归时,如果指数是1,那么在表示这一项时,我们写"2";如果指数是0,写"2(0)";如果指数>=2,那么我们就需要将这个指数用递归函数转换成字符串,然后用括号括起来,写在2的后面。 但是,我们看7的分解:7的二进制是111,也就是2^2 + 2^1 + 2^0,所以7的表示就是“2(2)+2+2(0)”。这里,对于指数2(对应2^2这一项),我们在表示的时候,指数2(大于等于2)所以写成2(指数部分),而指数部分就是这个2,但是2作为指数部分的输入时,递归函数如何处理2? 输入2:因为2=2^1,所以根据规则,2^1的表示就是“2”,所以在7的表示中,指数2的部分就写成2(2)?不对,因为指数2作为递归函数的输入,递归函数返回的是“2”(因为2=2^1,所以返回“2”),所以整体就是“2(2)”。 因此,递归函数的设计: 函数名:string powerOfTwo(int n) 功能:返回n的约定方次表示字符串(n>=0) 递归终止条件: if (n == 0) return "0"; // 但实际上n>=1,因为输入是正整数,分解时最小到0,但0次幂出现时我们返回"0" if (n == 1) return "2(0)"; if (n == 2) return "2"; // 因为2=2^1,指数1省略,所以直接写2 对于其他情况,我们将n分解为2的幂次和: 遍历n的二进制位,从高位到低位(或者低位到高位,但通常高位开始方便,因为要组合成字符串,但顺序不重要,因为每一项用加号连接,但注意题目没有要顺序,不过习惯上从高到低) 但是注意:分解后,每一项的指数就是该位的权值,然后我们递归处理这个指数(因为指数可能大于2,需要递归表示) 步骤: 1. 初始化一个空字符串res,用于保存结果。 2. 遍历每一位(从最高位到最低位,或者反过来,但题目没有要顺序,不过例子是从大到小),这里我们选择从高位开始(这样字符串中就是先高次后低次)。 3. 用mask或者移位操作,找到所有为1的位(即有效位)。 4. 对于每一个有效位,该位对应的指数是i(即2的i次方),那么这一项就是2^i。 我们需要将这一项表示为:如果i==1,则该项为"2";否则,该项为"2(" + powerOfTwo(i) + ")"。 但是注意:如果i==0,那么该项就是"2(0)"。 另外,如果i==1,我们直接写"2"(其实就是指数省略的情况)。 5. 但是,我们分解n得到的是多个2的幂次项的和,所以将这些项用"+"连接。 然而,这里有一个问题:当n=2时,按照分解,2的二进制是10,所以指数为1,那么我们就会得到一项:指数1,然后该项表示为"2",所以返回"2"。 当n=3:二进制11,即指数1和指数0,两项:指数1表示为"2",指数0表示为"2(0)",所以组合为"2+2(0)"。 当n=4:二进制100,指数2,那么该项表示为"2(" + powerOfTwo(2) + ")" -> powerOfTwo(2)返回"2",所以得到"2(2)"。 当n=5:101,指数2和指数0:所以是"2(2)+2(0)"。 但是,7:111 -> 指数2,1,0:所以表示为"2(2)+2+2(0)"。 137:二进制是10001001 -> 2^7 + 2^3 + 2^0 7的表示:powerOfTwo(7)返回"2(2)+2+2(0)",所以整个表示为"2(2(2)+2+2(0))"(因为指数7用递归得到字符串后,外面加上括号)? 不对,137的表示应该是:2(7)这一项,因为指数7>=2,所以写成2( powerOfTwo(7) ),而powerOfTwo(7)返回"2(2)+2+2(0)",所以这一项就是"2(2(2)+2+2(0))"。 同理,2^3项:指数3>=2,所以写成2( powerOfTwo(3) ),而powerOfTwo(3)返回"2+2(0)",所以该项为"2(2+2(0))"。 最后2^0项:就是"2(0)"。 所以整个字符串:2(2(2)+2+2(0)) + 2(2+2(0)) + 2(0) (注意用加号连接) 但是,我们如何保证顺序(从高到低)?在分解二进制的时候,我们是从最高位开始,所以顺序自然是从高到低。但是,我们也可以从低位开始,然后反转。不过,题目没有要顺序,但是例子中是从高到低。 然而,上述递归函数在输入为2时,返回"2",这是正确的;输入为3,返回"2+2(0)",但注意题目中137的7的表示是“2(2)+2+2(0)”,其中2(2)就是2^2=4,然后2^1=2,2^0=1,所以顺序是4+2+1,即从高到低。 因此,我们在遍历每一位时,从最高位开始(即从最大的指数开始)。 具体实现步骤(递归函数): if (n == 0) return "0"; if (n == 1) return "2(0)"; if (n == 2) return "2"; string res; int bit = 0; // 当前的位数(指数) int temp = n; vector<int> exponents; // 用来保存所有为1的位的指数(从高到低) // 或者我们遍历最高位:我们可以先找到最高位在哪 int highest = 0; while (temp) { highest++; temp >>= 1; } // 此时最高位在highest-1位 bool first = true; // 标记是否是第一项(用于加号) for (int i = highest-1; i >=0; i--) { if (n & (1 << i)) { // 第i位为1 if (!first) { res += "+"; } else { first = false; } if (i == 0) { res += "2(0)"; } else if (i == 1) { res += "2"; } else { // 指数i>=2,递归处理指数i res += "2(" + powerOfTwo(i) + ")"; } } } return res; 但是考虑n=3:二进制为11,最高位是1(即第1位和第0位),所以循环从1到0: i=1:为1,且是第一项,所以进入else if (i==1) -> "2",然后first=false i=0:为1,不是第一项,所以加号,然后进入if(i==0) -> "2(0)",所以结果"2+2(0)",正确。 但是注意:当n=4时,二进制100,最高位是2(即第三位,但我们的位是从0开始,所以指数是2),所以循环中i=2,然后进入else:因为i>=2,所以递归调用powerOfTwo(2),而powerOfTwo(2)返回"2",所以这一项就是"2(2)",正确。 但是,递归调用powerOfTwo(2)时,2满足条件n==2吗?是的,在powerOfTwo(2)中,直接返回"2",所以n=4的结果是"2(2)"。 但是,我们考虑一个复杂点的数:比如5(101): 最高位是2(即指数为2的位),然后指数0的位。 指数2:递归调用powerOfTwo(2)返回"2",所以该项为"2(2)" 指数0:直接写"2(0)" 所以结果是"2(2)+2(0)",正确。 现在,我们来测试137: 137的二进制:10001001 -> 8位,最高位是7(即第7位) 指数7:因为7>=2,所以递归调用powerOfTwo(7) 在powerOfTwo(7)中: 7的二进制:111,所以最高位是2(即第2位),然后第1位,第0位。 指数2:递归调用powerOfTwo(2) -> 返回"2",所以该项为"2(2)" 指数1:直接写"2" 指数0:写"2(0)" 所以7的表示是"2(2)+2+2(0)"(注意顺序:从高位到低位,所以指数2的项在最前,然后是指数1,然后是指数0) 因此,137的指数7项:2( powerOfTwo(7) ) -> "2(2(2)+2+2(0))" 然后,指数3:递归调用powerOfTwo(3) -> 3的二进制:11 -> 指数1和0:所以返回"2+2(0)",因此该项为"2(2+2(0))" 指数0:直接"2(0)" 最后组合:从高到低,所以:2(2(2)+2+2(0)) + 2(2+2(0)) + 2(0) (中间用加号连接) 但是,注意137的二进制中,指数7、指数3、指数0,所以三项,我们用加号连接,所以整个字符串为: "2(2(2)+2+2(0))+2(2+2(0))+2(0)" 然而,我们检查一下137的分解: 2(2(2)+2+2(0)) = 2^(2^2+2^1+2^0) = 2^(4+2+1)=2^7=128 2(2+2(0)) = 2^(2^1+2^0)=2^(2+1)=2^3=8 2(0)=1 128+8+1=137,正确。 但是,我们输出时,注意加号两边没有空格。 另外,我们需要注意递归的终止条件是否覆盖了所有情况。 但是,我们递归函数终止条件是n=0,1,2,那么当n=0时返回"0",但实际上在我们的分解中,只有当指数部分出现0时才会用到(即2(0)),而指数0不会作为外部输入。另外,输入n>=1,所以n=0不会在外部输入时出现,但是在递归中可能会出现指数0(即当指数为0时,我们调用powerOfTwo(0)),所以我们需要处理n=0的情况。 但是,我们在分解指数部分时,指数0出现在哪里?比如n=1时,我们直接返回"2(0)",所以不会调用powerOfTwo(0)。但是,如果我们输入0,那么返回"0",那么当指数部分出现0时,我们直接写"2(0)",并不会调用powerOfTwo(0)来生成0的表示。实际上,在指数部分,我们直接处理了0(即当指数i=0时,我们直接写"2(0)",而不是写成2( powerOfTwo(0) )),所以不会出现调用powerOfTwo(0)的情况。 同理,指数1我们也直接处理了。 因此,递归函数中,n>=0,但是我们在递归调用时,传入的指数i>=2(因为0和1直接处理了),而i最小为2,所以递归调用的参数最小为2。而2在终止条件中直接返回,所以不会无限递归。 但是,我们还需要注意,当指数i很大时,递归深度不会太深(因为指数i最大也就30左右,因为整数范围是2^31,所以指数最大为30)。 因此,我们按照上述思路编写C++代码。 代码结构: #include <iostream> #include <vector> #include <string> using namespace std; string powerOfTwo(int n) { if (n == 0) return "0"; if (n == 1) return "2(0)"; if (n == 2) return "2"; // 找到最高位 int temp = n; int highest = 0; while (temp) { highest++; temp >>= 1; } // 此时最高位是highest-1 string res = ""; bool first = true; for (int i = highest-1; i >=0; i--) { if (n & (1 << i)) { // 第i位为1 if (!first) { res += "+"; } else { first = false; } if (i == 0) { res += "2(0)"; } else if (i == 1) { res += "2"; } else { // 递归处理指数i res += "2(" + powerOfTwo(i) + ")"; } } } return res; } int main() { int num; cout << "请输入一个正整数: "; cin >> num; cout << num << " = " << powerOfTwo(num) << endl; return 0; } 但是,我们测试一下: 输入2:输出"2",正确。 输入3:输出"2+2(0)",正确。 输入4:输出"2(2)",正确。 输入5:输出"2(2)+2(0)",正确。 输入7:输出"2(2)+2+2(0)",正确。 输入137:输出"2(2(2)+2+2(0))+2(2+2(0))+2(0)",符合预期。 但是,注意题目要的是“方次表示法”,我们这样输出是正确的。 但是,有没有可能优化?比如,当指数部分是一个简单的表达式?例如,4=2^2,所以直接写成2(2)而不是2(2(2))?但是我们的递归函数在处理4时,因为4的二进制是100,所以指数2,而2作为指数输入时,递归调用powerOfTwo(2)返回"2",所以4的表示就是2(2),并没有递归成2(2(2))。因为2作为指数输入时,我们在powerOfTwo(2)中直接返回"2",所以4的表示就是2(2),正确。 所以,代码实现如上。 另外,注意整数范围,这里我们假设输入的整数在int范围内(即不超过2^31-1)。由于指数最大为30(因为2^30在int范围内,2^31就超过int了,但n最大为2^31-1,所以最高位指数为30),所以递归深度最多30层,不会栈溢出。 但是,在计算最高位时,我们使用了一个循环,这个循环最多32次(32位整数),所以效率没问题。 因此,我们按照上述代码实现。 </think> ### 将正整数用2的幂次方表示并按约定方次表示法转换 在数学中,任意正整数都可以唯一表示为若干个互不相同的2的幂次方之和(二进制表示)。约定方次表示法要: - $2^0$ 表示为 `2(0)` - $2^1$ 表示为 `2` - $2^2$ 表示为 `2(2)` - 指数 $\geq 2$ 时需递归表示(如 $2^3 = 2^{(2+1)} = 2(2+2(0))$) #### 算法思路 1. **递归分解**:将数字分解为2的幂次和 2. **指数处理**: - 指数=0 → `2(0)` - 指数=1 → `2` - 指数≥2 → 递归处理指数并包裹在 `2(...)` 中 3. **组合结果**:用加号连接各项(从高次到低次) #### C++代码实现 ```cpp #include <iostream> #include <string> using namespace std; string powerExpression(int n) { if (n == 0) return "0"; if (n == 1) return "2(0)"; if (n == 2) return "2"; // 计算二进制最高位 int temp = n; int highest_bit = 0; while (temp >>= 1) highest_bit++; string res; bool first_term = true; for (int i = highest_bit; i >= 0; i--) { if (n & (1 << i)) { // 检查第i位是否为1 if (!first_term) res += "+"; else first_term = false; if (i == 0) { res += "2(0)"; } else if (i == 1) { res += "2"; } else { res += "2(" + powerExpression(i) + ")"; } } } return res; } int main() { int num; cout << "请输入正整数: "; cin >> num; cout << num << " = " << powerExpression(num) << endl; return 0; } ``` #### 示例解析 - **输入137**: - 分解:$137 = 2^7 + 2^3 + 2^0$ - 递归处理: - $7 = 2^2 + 2^1 + 2^0 → \text{2(2)+2+2(0)}$ - $3 = 2^1 + 2^0 → \text{2+2(0)}$ - 最终输出: `2(2(2)+2+2(0)) + 2(2+2(0)) + 2(0)` - **验证**: - $2^{(4+2+1)} = 2^7 = 128$ - $2^{(2+1)} = 2^3 = 8$ - $2^0 = 1$ - $128 + 8 + 1 = 137$ #### 复杂度分析 - **时间复杂度**:$O(\log n)$,二进制位数决定递归深度 - **空间复杂度**:$O(\log n)$,递归栈深度 此方法严格遵循约定方次表示法,通过递归处理指数实现精确转换[^1]。 --- ### 相关问题 1. **如何证明正整数都能用2的幂次方唯一表示?** [提示:参考二进制表示的唯一性定理] 2. **如何处理指数为负数的幂次方表示?** [提示:需扩展约定规则至分数领域] 3. **递归算法表示超大整数(如$10^{18}$)时会出现什么性能问题?如何优化?** [提示:考虑尾递归优化或迭代实现] 4. **是否存在非递归的位运算实现方案?** [提示:尝试用循环代替递归处理二进制位] [^1]: 参考《计算机程序设计艺术》卷1-基本算法中二进制表示相关章节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值