ACM总结1--入门数学题

本文深入探讨了算法优化的关键技巧,包括防止数据溢出的方法,如调整乘法运算顺序;介绍了GCD(最大公约数)和LCM(最小公倍数)的性质及欧几里德算法的高效实现;并详细讲解了快速幂算法的原理与应用,通过实例演示如何减少计算复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、防止隐藏的数据溢出

例题: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=(3
3)(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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值