一、防止隐藏的数据溢出
例题:SUM(n) = 1 + 2 + 3 + … + n
You may assume the result will be in the range of 32-bit signed integer.
Sample input:
10
Sample output:
55
http://acm.hdu.edu.cn/showproblem.php?pid=1001
当我们用SUM(n) = 1 + 2 + 3 + … + n
= n * (n+1) / 2
来做时可能会出现n*(n+1)超出32位,造成数据溢出,而写成n /2* (n+1) 则不会溢出,所以做乘法时要注意有没有可能会溢出。
二、LCM和GCD
本文给出了GCD、LCM的性质,以及欧几里德算法的实现和时间复杂度。欧几里得算法 (Euclidean algorithm) ,即大部分选手所知的“辗转相除法”,其核心在于不断将两数规模变小,最后实现对数时间内求解两个数的最大公约数。
其核心是:
gcd(a,b)=gcd(b,a%b)
最大公约数:即最大公因子,能够同时整除a和b的最大因子,记作gcd(a, b),或gcd
最小公倍数:能够被a和b整除的最小数,记作lcm(a, b),或lcm。
一些性质:
a, b都能分解为有限个素数的积
gcd(a, b)中只含a,b的全部公共素因子
lcm(a, b)中含有a,b的所有素因子
gcd%lcm=0gcd%lcm=0
gcd∗lcm=a∗bgcd∗lcm=a∗b
lcm/gcd=a/gcd∗b/gcdlcm/gcd=a/gcd∗b/gcd
gcd(ka,kb)=k∗gcd(a,b)gcd(ka,kb)=k∗gcd(a,b)
lcm(ka,kb)=k∗lcm(a,b)lcm(ka,kb)=k∗lcm(a,b)
lcm(a/b,c/d)=lcm(a,c)/gcd(b,d)
详细算法
等式gcd(a,b)=gcd(b,a%b)gcd(a,b)=gcd(b,a%b)可以理解为一个数减小,再两个数交换。 因为两个数的值经过循环不断变小,在结束循环前,两个数不可能小于0,且不可能同时为0。 所以最后b先变为0,且有
gcd(an,bn)=⋯=gcd(an−1,bn−1)=gcd(a0,0),a0=gcd(a,b)
/*递归算法*/
int gcd(int a, int b) {
return !b ? a : gcd(b, a%b);
}
/*递推算法*/
int gcd(int a, int b) {
while (b != 0) {
int c = a % b;
a = b;
b = c;
}
return a;
}
平均时间复杂度大约为lnN(N为较小那个数)
三、快速幂
一般快速幂的题目都是取答案的最后几位,所以我们先看三个取模运算的法则。
( a + b ) % c = ( ( a % c ) + ( b % c ) ) % c
( a * b ) % c = ( ( a % c ) * ( b % c ) ) % c
( a – b ) % c = ( ( a % c ) – ( b % c ) ) % c
快速幂算法能帮我们算出指数非常大的幂,传统的求幂算法之所以时间复杂度非常高(为O(指数n)),就是因为当指数n非常大的时候,需要执行的循环操作次数也非常大。所以我们快速幂算法的核心思想就是每一步都把指数分成两半,而相应的底数做平方运算。这样不仅能把非常大的指数给不断变小,所需要执行的循环次数也变小,而最后表示的结果却一直不会变。
举个例子:
3^10=3333333333
3^10=(33)(33)(33)(33)(33)
3^10=(33) ^5
3^10=9 ^5
这里5是一个奇数,5的一半是2.5,但是指数不能为小数,因此我们不能直接执行5/2,还有另一种方法能表示9^5
9^5 = ( 9 ^4)( 9 ^1)
这个9^ 1 我们先单独移出来,剩下的9^4又能够在执行“缩指数”操作了
9^5=(81 ^2)(9 ^1)
9^5=(6561 ^1)(9 ^1)
9^5=(6561 ^0)(9 ^1)(6561 ^1)=1*(9 ^1)(6561 ^1)=(9 ^1)(6561 ^1)=9*6561=59049
所以我们能发现一个规律:最后求出的幂结果实际上就是在变化过程中所有当指数为奇数时底数的乘积。
int fastPower(long long base, long long power) {
base %= 10; //防止溢出
int result = 1;
while (power > 0) {
if (power % 2 == 0) {
//如果指数为偶数
power = power / 2;//把指数缩小为一半
base = base * base % 10;//底数变大成原来的平方,此处假设只需要答案最后一位
} else {
//如果指数为奇数
power = power - 1;//把指数减去1,使其变成一个偶数
result = result * base % 10;//此时记得要把指数为奇数时分离出来的底数的一次方收集好
power = power / 2;//此时指数为偶数,可以继续执行操作
base = base * base % 10;
}
}
return result;
}
优化
因为power是一个整数,我们直接用power/2代替判断(power - 1)/2。在C语言中,power%2==1可以用更快的“位运算”来代替。同样,对于power=power/2来说,也可以用更快的“位运算”进行替代。
//递推代码:
int fastPower(long long base, long long power) {
base %= 10; //防止溢出
int result = 1;
while (power > 0) {
if (power & 1) { //此处等价于if(power%2==1)
result = (result * base % 10;
}
power >>= 1; //此处等价于power=power/2
base = (base * base) % 10;
}
return result;
}
//注:此代码只保留数据最后一位,所以不会溢出
//递归代码(未取模):
int fastPower(long long base, long long power){
int result;
if(power==0) result=1;
else{
result=fastpower(base*base, power/2);
if(power%2==1) resullt*=base;
}
return result;
}
//注:此代码要考虑数据溢出
总结
总结: 对于ACM简单题目,千万不能蛮干!实际上,看到题目的数据规模,很快就能知道:这类题目有规律可循,可以尝试利用打表来解决。数据规模不大的要考虑数据是否会溢出,输出格式问题。
2019.10.23
plussone