0分,TLE

P1591 阶乘数码

lzy20091001 @ 2023-06-08 23:55:39

大致看了一下题解,和我想的一样就是一道高精乘低精的模板题啊?阶乘也用递推优化过了,怎么会超时呢?

代码中的add和mul完全是照模板写的,求助

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

string add(string, string);
string mul(string, int);

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int t;
    int n, maxn = 1;
    char a;
    string fac[1001] = {"0", "1"};
    cin >> t;
    for (int i = 1; i <= t; i++)
    {
        cin >> n >> a;
        if (n <= maxn)
            cout << count(fac[n].begin(), fac[n].end(), a) << "\n";
        else
        {
            for (int j = maxn + 1; j <= n; j++)
                fac[j] = mul(fac[j - 1], j);
            maxn = n;
            cout << count(fac[n].begin(), fac[n].end(), a) << "\n";
        }
    }
    return 0;
}

string add(string str1, string str2)
{
    string str = "";
    int str1_len = str1.length(), str2_len = str2.length(), temp = 0;
    bool carry = 0;
    if (str1_len < str2_len)
        for (int i = 1; i <= str2_len - str1_len; i++)
            str1 = '0' + str1;
    else
        for (int i = 1; i <= str1_len - str2_len; i++)
            str2 = '0' + str2;
    str1_len = str1.length(), str2_len = str.length();
    for (int i = str1_len - 1; i >= 0; i--)
    {
        temp = str1[i] - '0' + str2[i] - '0' + carry;
        carry = temp / 10;
        temp %= 10;
        str = char(temp + '0') + str;
    }
    if (carry == 1)
        str = '1' + str;
    return str;
}

string mul(string a, int b)
{
    string str = "", temp_num = "";
    int temp_digit = 0;
    int a_len = a.length(), carry = 0;
    for (int i = a_len - 1; i >= 0; i--)
    {
        temp_num = "";
        temp_num.insert(0, a_len - 1 - i, '0');
        temp_digit = (a[i] - '0') * b + carry;
        carry = temp_digit / 10;
        temp_digit %= 10;
        temp_num = char(temp_digit + '0') + temp_num;
        str = add(str, temp_num);
    }
    if (carry != 0)
        str = to_string(carry) + str;
    str.erase(0, str.find_first_not_of('0'));
    if (str.empty())
        str = "0";
    return str;
}

by _xm_ @ 2023-06-09 01:47:11

string mul(string a, int b)
{
    string str = "", temp_num = "";
    int temp_digit = 0;
    int a_len = a.length(), carry = 0;
    for (int i = a_len - 1; i >= 0; i--)
    {
        temp_num = "";
        temp_num.insert(0, a_len - 1 - i, '0'); // 这行代码有问题!!
        temp_digit = (a[i] - '0') * b + carry;
        carry = temp_digit / 10;
        temp_digit %= 10;
        temp_num = char(temp_digit + '0') + temp_num; // 这行代码也有问题!!
        str = add(str, temp_num);
    }
    if (carry != 0)
        str = to_string(carry) + str;
    str.erase(0, str.find_first_not_of('0'));
    if (str.empty())
        str = "0";
    return str;
}

insert()的时间复杂度是 O(n)string加法也是 O(n),加上外层的循环,整体就是 O(n^2)了,这比正常高精乘低精的复杂度 O(n) 慢太多了。

究其原因是你的程序中数字是正着存储的(第一位是最高位,最后一位是最低位),这导致对齐个位需要 O(n) 的时间,极大的拖慢了运算的速度,最后喜提TLE

然而反着存储数字(第一位是最低位,最后一位是最高位)因为天然就对齐了个位,便不存在所谓的“对齐”一说了,只需O(1)便可完成。


by lzy20091001 @ 2023-06-09 19:37:09

@xm 感谢赐教!

不过还有一个疑问想请教一下

循环中使用的temp_num.insert(0, a_len - 1 - i, '0');是模拟乘法竖式中补0的部分

以25*25为例(我不太会格式请海涵):

  25
* 25
------
 125
 500
------
 625

在计算25*20的时候,我计算了25*2,并在后面补了一个0

这个insert()就是补0的意思

正序存储的话怎么省掉这个insert()啊?没太看懂。


by _xm_ @ 2023-06-09 22:52:47

emm...

一般我是这样列竖式的:

  25
* 25
------
 125
 50   //这个0是可以省略的
------
 625

计算25 * 25的过程中,问题被拆解成5 * 25 + 20 * 25计算;而在计算20 * 25时,问题又可以变成2 * 25

分析一下:

第一轮:
5 * 25 = 125
存入数组
 3  2  1    // 下标
[0][0][125] // 填入数组
        |   // 从这里填入计算结果
[1][2][5]   // 进位处理之后

第二轮:
2 * 25 = 50 (拆解后的20 * 25)
 3  2  1   // 下标
[1][52][5] // 填入数组
    |      // 从这里填入计算结果
[1][6][5]  // 进位处理之后

因为第二轮省略了一个零,要在第一轮的位置上左移 1才可以填入数组。

然后可以推导出一般情况:

i 轮就从 a[i] 开始填进数组,然后处理进位

因为是高精乘低精,“个位数” \timesint类型”,结果不超过1 \times 10^{10},每次进位处理不会超过 10 次,每一轮 for 循环自然就是 O(1)

此时整个 for 循环的时间复杂度是 O(n) ,问题完美解决!!


|