HDU_ACM step 1.2.2 Rightmost Digit (解法:快速幂取余和快速幂取余的推导过程)

版权所有,欢迎转载,转载请注明出处,谢谢微笑

Rightmost Digit

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 6293 Accepted Submission(s): 1645
 
Problem Description
Given a positive integer N, you should output the most right digit of N^N.
 
Input
The input contains several test cases. The first line of the input is a single integer T which is the number of test cases. T test cases follow.
Each test case contains a single positive integer N(1<=N<=1,000,000,000).
 
Output
For each test case, you should output the rightmost digit of N^N.
 
Sample Input
2
3
4
 
Sample Output
7
6

<div style="" font-size:="" 14px;="" border-top:="" #b7cbff="" 1px="" dashed;="" font-family:="" times"="">
       
       
       
       
Hint
In the first case, 3 * 3 * 3 = 27, so the rightmost digit is 7. In the second case, 4 * 4 * 4 * 4 = 256, so the rightmost digit is 6.


如果直接根据题意来做,会超时。为了降低时间复杂度,这道题主要是要利用快速幂取模的算法思想


快速幂取模算法:

我们先从简单的例子入手:求a^b % c = ?

算法1.首先直接地来设计这个算法:

int ans = 1;

for(int i = 1;i<=b;i++)

{

ans = ans * a;

}

ans = ans % c;

这个算法的时间复杂度体现在for循环中,为Ob.这个算法存在着明显的问题,如果ab过大,很容易就会溢出。

那么,我们先来看看第一个改进方案:在讲这个方案之前,要先有这样一个公式:a^b%c=(a%c)^b%c.这个公式大家在离散数学或者数论当中应该学过,不过这里为了方便大家的阅读,还是给出证明:

引理1:a^b%c = (a%c)^b%c

 

上面公式为下面公式的引理,即积的取余等于取余的积的取余。

 

证明了以上的公式以后,我们可以先让a关于c取余,这样可以大大减少a的大小,

于是不用思考的进行了改进:

算法2

int ans = 1;

a = a % c; //加上这一句

for(int i = 1;i<=b;i++)

{

ans = ans * a;

}

ans = ans % c;

聪明的读者应该可以想到,既然某个因子取余之后相乘再取余保持余数不变,那么新算得的ans也可以进行取余,所以得到比较良好的改进版本。

算法3

int ans = 1;

a = a % c; //加上这一句

for(int i = 1;i<=b;i++)

{

ans = (ans * a) % c;//这里再取了一次余

}

ans = ans % c;

这个算法在时间复杂度上没有改进,仍为O(b),不过已经好很多的,但是在c过大的条件下,还是很有可能超时,所以,我们推出以下的快速幂算法。

快速幂算法依赖于以下明显的公式,我就不证明了。

 

那么我们可以得到以下算法:

算法4

int ans = 1;

a = a % c;

if(b%2==1)

ans = (ans * a) mod c; //如果是奇数,要多求一步,可以提前算到ans

k = (a*a) % c; //我们取a2而不是a

for(int i = 1;i<=b/2;i++)

{

ans = (ans * k) % c;

}

ans = ans % c;

 

我们可以看到,我们把时间复杂度变成了O(b/2).当然,这样子治标不治本。但我们可以看到,当我们令k = (a * a) mod c时,状态已经发生了变化,我们所要求的最终结果即为(k)b/2 mod c而不是原来的ab mod c所以我们发现这个过程是可以迭代下去的。当然,对于奇数的情形会多出一项a mod c,所以为了完成迭代,当b是奇数时,我们通过

ans = (ans * a) % c;来弥补多出来的这一项,此时剩余的部分就可以进行迭代了。

 

形如上式的迭代下去后,当b=0时,所有的因子都已经相乘,算法结束。于是便可以在Olog b的时间内完成了。于是,有了最终的算法:快速幂算法。

算法5:快速幂算法

 

int ans = 1;

a = a % c;

while(b>0)

{

if(b % 2 == 1)

ans = (ans * a) % c;

b = b/2;

a = (a * a) % c;

}

将上述的代码结构化,也就是写成函数:

int PowerMod(int a, int b, int c)

{

int ans = 1;

a = a % c;

while(b>0)

{

if(b % 2 = = 1)

ans = (ans * a) % c;

b = b/2;

a = (a * a) % c;

}

return ans;

}

本算法的时间复杂度为Ologb),能在几乎所有的程序设计(竞赛)过程中通过,是目前最常用的算法之一。


下面给出笔者实现该题目的代码:

#include<iostream>
#include<string>
using namespace std;

/*
int mod_exp(int a, int b, int c)
{
	int res,i,k;
	res = 1 % c;
	k = a * a % c;
	b >>= 1;
	for( i=0; i< b; i++)
	{
		if ( b & 1)//判断是否为奇数
			res = res * a % c;
		res = res * k % c;
	}
	return res;
}
*/

int mod_exp(int a, int b, int c)
{
	int res,i;
	res = 1 % c;
	a = a % c;	
	while(b)
	{
		if ( b & 1) //判断是否为奇数
			res = res * a % c;
		a = a * a % c;
		b >>= 1;
	}
	return res;
}

int main()
{
	int i;
	cin>>i;
	while(i--)
	{
		int n;
        cin >> n;
        cout << mod_exp(n, n, 10) << endl;
	}
	return 0;
}



你可能感兴趣的:(Algorithm,算法,ACM,快速幂取余)