更新:2014-02-10 凌晨
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
来源:http://acm.fzu.edu.cn/problem.php?pid=1759
概述:计算a^b mod c,但b是个相当大的数,可以达到100万位。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
理论分析
首先要知道欧拉函数,可以参看FOJ 1012题的解答。
然后,这里直接套用以下公式:
注意,有的文章提到公式使用的条件是b≥φ(c),其实上述公式是没有这个限制的。只不过b<φ(c)时,如果利用公式计算,反而增大了幂值,在最终采用逐次平方法求解时运算量变大。
公式的证明,可以参阅这两篇文章:
http://www.narutoacm.com/archives/a-pow-b-mod-m/
http://hi.baidu.com/aekdycoin/item/e493adc9a7c0870bad092fd9。
我也补充点对该公式的理解:当a与c互素时,用欧拉定理,
截图来自:陈景润,《初等数论II》第五章,百度网盘下载
就能很容易证明
当a与c不互素时,后面的指数部分,就多加了φ(c),变成b%φ(c)+φ(c)。即上述的“求幂大法”,我认为是在欧拉定理的基础上,进行的推广应用。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
编程分析
1、欧拉函数直接套用FOJ1012的模板。
2、b是个大数,b%φ(c)要使用大数取余运算,这个其实不难。我把大数存在字符串str,把最终值存在b,在下述代码中第42行,一行for循环就可以完成该运算。
3、最终得到的b,一定是10亿以内的数,直接采用普通的快速幂模模板——逐次平方法求解。
#include <cstdio>
#include <cstring>
using namespace std;
typedef __int64 i64;
i64 pow(i64 a, i64 b, i64 c)
{
i64 p = 1;
while (b)
{
if (b&1) p = p * a % c;
b >>= 1;
a = a * a % c;
}
return p;
}
i64 euler(i64 n)
{
i64 i, ans = 1;
for (i = 2; i*i <= n; i++)
{
if (n%i==0)
{
ans *= i-1, n /= i;
while (n%i==0) ans *= i, n /= i;
}
}
if (n!=1) ans *= n-1;
return ans;
}
int main()
{
i64 a, b, c;
char str[1000001];
while (scanf("%I64d%s%I64d", &a, &str, &c) != EOF)
{
int len = strlen(str), phi = euler(c), i;
for (b = i = 0; i < len; i++) b = (b*10 + str[i] - '0') % phi;
b += phi;
printf("%I64d\n", pow(a, b, c));
}
return 0;
}
花絮:我试着把b += phi;删掉,然后提交一次,发现AC了,说明测试数据还不够严谨,这句话本来是必须的,否则就直接变成欧拉定理了,而欧拉定理成立的条件是a与c互素。反例:输入的a b c分别为4 1 2。如果去掉第43行代码,输出为1,而实际应该输出0。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
效率分析
上述代码中,没有对b的范围进行分类求解,因为那样会带来不必要的编程麻烦,效率未必会更高。
上述代码AC用时125ms。如果加上#include <cstdlib>,使用atof和atoi函数,把主函数改成如下形式,则用时140ms,效率不减反增了。这里写这代码主要是为了学习下atof和atoi函数。
int main()
{
i64 a, b, c;
char str[1000001];
while (scanf("%I64d%s%I64d", &a, &str, &c) != EOF)
{
int len = strlen(str), phi = euler(c), i;
if (atof(str) > phi)
{
for (b = i = 0; i < len; i++) b = (b*10 + str[i] - '0') % phi;
b += phi;
}
else b = atoi(str);
printf("%I64d\n", pow(a, b, c));
}
}
从效率角度讲,可以这样优化算法:
int main()
{
i64 a, b, c;
char str[1000001];
while (scanf("%I64d%s%I64d", &a, &str, &c) != EOF)
{
int len = strlen(str), phi = euler(c), i;
if (len > 10)
{
for (b = i = 0; i < len; i++) b = (b*10 + str[i] - '0') % phi;
b += phi;
}
else b = atoi(str);
printf("%I64d\n", pow(a, b, c));
}
}
不过这段代码用时也是125ms,并没有太大的提高。比赛中,只要能AC,也不会在意这一点点时间差,既然公式能通用,就不必要分类讨论了。