快速幂应用之剪绳子问题

  • Post author:
  • Post category:其他


有这样一类问题,给你一个长度为n的绳子,要求你可以剪切任意次数,分为任意段,使得这些子段长度的乘积最大。我们把这类问题暂时先称为剪绳子,这种问题的解法也很简单,通过数学证明可以得出,我们优先剪长度为3的子段,假设剪了x个3后,剩余的长度小于3时,有以下三种情况:

情况一:剩余长度为0,则此时最优,答案为3的x次方。

情况二:剩余长度为1,此时我们应该少剪一个3,即剪切x-1个3,剩余长度为4,此时结果最优,答案为3的x-1次方乘以4。

情况三:剩余长度为2,此时也是最优,答案为3的x次方乘以2。

因此很容易写出代码,这个问题就可以提交了,代码如下

#include <iostream>
using namespace std;
long long M = 1000000007;

int main() {
    long long n;
    cin >> n;
    // x表示的是n分解出来的3的个数
    long long x = n / 3;
    int y = n % 3;  // y表示的是分为x个3后的余数
    /*
     * 如果y=0,则说明刚好分为x个3
     * 如果y=1,则需要调整一下,拿出一个3,和1加起来是4,这样分为x-1个3和1个4是最优解
     * 如果y=2,则x个3和1个2刚好是最优解
     * */
    if (y == 1) {
        y = 4;
        x--;  // 拿出之前分解的一个3,和剩余的一个1组合在一起,这样结果最优
    }
    long long ans = y;
    if (ans == 0) ans = 1;   // 这里防止y=0的情况,如果y为0,则需要将ans置1,因为后面都是乘法运算
    for (int i = 0; i < x; i++) {
        ans *= 3;  // 暴力解决
        if (ans >= M) ans %= M;
    }
    return 0;
}

但是此时又有一个问题,如果给的n特别大,比如,n<=10^18,那么如果用暴力的循环x次求解,则一定会超时,所以此时需要借助快速幂思想来求解。

思考一下,现在想计算a^b,如何求解,可以第一时间想到库函数,比如c++中,可以通过pow(a, b)计算,但是b特别大,比如b=10^17,此时pow函数肯定会越界,一般对于这种大数量级的幂运算,很容易越界,所以题目会要求你对某个数取余,最常见的是对M=10^9+7取余。所以需要在运算过程中动态对M取余,快速幂的思想是,每次将底数a更新为a*a,指数b减半,过程如下:

第一步:
a^{b}=(a^{2})^{\frac{b}{2}}

第二步:我们需要将
a^{2}
看成一个整体
a_{1}
,把
\frac{b}{2}
看成一个整体
b_{1}
,继续运用第一步的思想可以得到:
(a^{2})^{\frac{b}{2}}=(a_{1})^{b_{1}}=(a_{1}^{2})^{\frac{b_{1}}{2}}

依照上面的步骤,指数每次折半,底数每次变为平方,时间复杂度为logn。

上面有个问题,如果指数为奇数,怎么办?在c++中,比如指数b=7,那么7/2=3,会丢掉一个底数,所以分情况,如果指数为奇数,那么
a^{b}=a*(a^{2})^{\frac{b}{2}}
,如果为偶数,则
a^{b}=(a^{2})^{\frac{b}{2}}

代码如下:

#include <iostream>
using namespace std;
long long M = 1000000007;

int main() {
    long long n;
    cin >> n;
    // x表示的是n分解出来的3的个数
    long long x = n / 3;
    int y = n % 3;  // y表示的是分为x个3后的余数
    /*
     * 如果y=0,则说明刚好分为x个3
     * 如果y=1,则需要调整一下,拿出一个3,和1加起来是4,这样分为x-1个3和1个4是最优解
     * 如果y=2,则x个3和1个2刚好是最优解
     * */
    if (y == 1) {
        y = 4;
        x--;  // 拿出之前分解的一个3,和剩余的一个1组合在一起,这样结果最优
    }
    long long ans = y;
    if (ans == 0) ans = 1;   // 这里防止y=0的情况,如果y为0,则需要将ans置1,因为后面都是乘法运算
    
    // 通过快速幂解决超时问题
    long long tmp = 3;  // tmp为上面说的底数,每次循环都会更新为自己的平方,因此tmp的值变化依次为:3,9,27,81,....
    // 在整个过程中,用到快速幂解决超市
    // tmp为底,底会指数增大,x为幂,会指数减少
    while(x > 0) {
        // 对于奇数次幂,如果要多于的底数先与结果ans做运算
        // 注意一个点:对于任何指数b>=2的数字,每次除以2,最终b一定会等于1,那么此时,指数为奇数,会进入if分支,将底数乘给最终结果ans;
        if(x % 2 == 1) {
            ans *= tmp;
            ans %= M;
        }
        x /= 2;
        tmp *= tmp;
        tmp %= M;
    }
    cout << ans;
    return 0;
}

我们拿一个case走一下代码,看看中间过程值分别是多少,比如3^7,此时tmp=3,x=7;

进入代码我们看每次循环的各个变量的值

循环前:x=7,ans=1,tmp=3

第1次循环后:x=3,ans=ans*tmp=3=
3^{1}
,tmp=tmp*tmp=9=
3^{3}

第2次循环后:x=1,ans=ans*tmp=27=
3^{3}
,tmp=tmp*tmp=81=
3^{4}

第3次循环后:x=0,ans=ans*tmp=2187=
3^{7}
,tmp=tmp*tmp=6561=
3^{8}

循环结束,输出ans=2187

有个题目正好用来练手:

OnlineJudge



版权声明:本文为zpf123456789zpf原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。