题目的主要信息:

  • 把一根长度为nn的绳子分成mm段,每段长度都是整数
  • 求每段长度乘积的最大值
  • 由于答案过大,请对 998244353 取模
  • 进阶要求:空间复杂度:O(1)O(1), 时间复杂度:O(log2n)O(log_2n)

数学推算

根据均值不等式,有:n1+n2+...+nmmn1n2...nmm\frac{n_1+n_2+...+n_m}{m}\geq \sqrt[m]{n_1n_2...n_m},等号当且仅当n1=n2=n3=...nmn_1=n_2=n_3=...n_m时成立,因为加法部分和是固定的绳子总长度,因此要使乘积最大,应该以相等的长度等分成多段。

如果将绳子按照每段长度为xx等分成mm段,则n=mxn=mx,乘积为xmx^m,因为有xm=xnx=(x1x)nx^m=x^{\frac{n}{x}}=(x^{\frac{1}{x}})^n因此当x1xx^{\frac{1}{x}}取最大值时取最大值。

y=x1xy=x^{\frac{1}{x}},即求这个函数的极值即可直到绳子等分成长度为多少可以使乘积最大。根据取对数、求导、求极值等一系列数学操作,得驻点为x0=ex_0=e,即极大值需要将绳子分成每段e,但是绳子长度只能是整数,靠近e的只有2 和3,二者分别代入公式,发现当x=3x=3是,乘积达到最大。

因此后续,使用贪心思想,不断将绳子分成每段长度为3即可,不足3的可以考虑,如果最后剩余的是2,直接乘上,如果最后剩余的是1,则取出一个3组成4分成长度为2的两段,因为22>132*2>1*3

方法一:暴力乘法(超时)

具体做法:

不超过3的直接计算,超过3的不断累乘3,然后number减去3,直到最后不超过4。

class Solution {
public:
    long long cutRope(long long number) {
        if(number <= 3) //不超过3直接计算
            return number - 1;
        long long res = 1;
        while(number > 4){
            res *= 3; //连续乘3
            res = res % 998244353;
            number -= 3; 
        }
        return res * number % 998244353;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n),其中nn为绳子的长度,最坏需要计算n/3n/3
  • 空间复杂度:O(1)O(1),无额外空间

方法二:快速幂+快速乘法

具体做法:

方法一是计算累成,其实我们可以直接分成3种情况,计算幂。

情况一:n整除3,即可以全部完成分成长度为3的小段,一共n/3n/3段,计算3n/33^{n/3}即可;

情况二:n除3余1,需要拿出一个3个1组合称,一共n/31n/3-1段长度为3的,2段长度为2的,计算223n/312*2*3^{n/3-1}即可;

情况三:n除3余2,直接将剩下长度为2的段乘在之前的乘积上,计算23n/32*3^{n/3}即可。

计算幂为了缩短时间,采用快速幂加快速乘法优化:

快速幂。如果我们要计算5105^{10},常规的算法是55=255*5=25,然后再255=12525*5=125,如此往下,一共是99次运算,即n1n-1次。但是我们可以考虑这样:55=255*5=25(二次)、2525=62525*25=625(四次)、625625=...625*625=...(八次),这是一个二分的思维,运算次数缩减到了log2nlog_2n次,公式如下: 图片说明

快速乘法。直接计算xax^a会超出long long的表示范围,因此我们可以考虑用加法来代替乘法,并在这其中取模。就比如ab=a(b1+b2+b3+...)a*b=a*(b_1+b_2+b_3+...),其中bib_i是数字bb的二进制各位,假设a=5a=5b=110101b=110101,我们有ab=a(1000001+100001+10000+1001+100+11)a*b=a*(100000*1+10000*1+1000*0+100*1+10*0+1*1),如下表所示可以换成加法运算并在加法中取模: 图片说明

class Solution {
public:
    long long mod = 998244353;
    long long fast(long long x, long long y){ //快速乘法
        long long res = 0;
        x %= mod;
        y %= mod;
        while(y){
            if(y & 1){
                res += x; //加法代替乘法,防止爆long long
                if(res >= mod)
                    res -= mod;
            }
            y = y >> 1;
            x = x << 1;
            if(x >= mod)
                x -= mod;
        }
        return res;
    }
    long long Pow(long long x, long long y){ //快速幂
        long long res = 1;
        while(y){
            if(y & 1) //可以再往上乘一个
                res = fast(res, x);
            x = fast(x, x); //叠加
            y = y >> 1; //减少乘次数
        }
        return res;
    }
    long long cutRope(long long number) {
        if(number <= 3) //不超过3直接计算
            return number - 1;
        if(number % 3 == 0) //能整除3
            return Pow(3, number / 3);
        else if(number % 3 == 1) //最后剩余1
            return fast(Pow(3, number / 3 - 1), 4); //4*3^{n-1}
        else //最后剩余2
            return fast(Pow(3, number / 3), 2); //2*3^n
    }
};

复杂度分析:

  • 时间复杂度:O(log2n)O(log_2n),快速幂相当于二分法,因此复杂度为O(log2n)O(log_2n)
  • 空间复杂度:O(1)O(1),无额外空间