0

我正在尝试在 C 中创建一个递归函数,计算 2ⁿ 中的数字总和,其中 n < 10⁷。我做了一些有用的东西,但它很慢(对于 n = 10⁵ 它需要 19 秒)。该函数必须在最多 1 秒内返回总和。我的算法计算 2ⁿ 使用数组来存储它的数字,它没有使用递归函数。

有没有办法在不计算 2ⁿ 的情况下计算这个数字总和?还是一种更快的方法来计算 2ⁿ 及其数字总和?

PS:递归函数必须只获取n参数,即int f(int n);

后期编辑:我写了一个递归解决方案;它更快,但它不适用于 n > 10⁵。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int sumOfDigits(int* num, int n) {
    if (n == 0) {
        int sum = 0;
        for (int i = 1; i <= num[0]; ++i) {
            while (num[i] > 0) {
                sum += num[i] % 10;
                num[i] /= 10;
            }
        }
        return sum;
    }

    int carry = 0;
    for (int i = 1; i <= num[0]; ++i) {
        num[i] = num[i] * 2 + carry;
        carry = num[i] / 1000000000;
        num[i] %= 1000000000;
        if (carry != 0 && i == num[0]) {
            ++num[0];
        }
    }

    return sumOfDigits(num, n - 1);
}

int main (void) {
    int n = 100000;
    int size = (n*log10(2) + 1) / 9 + 2;

    int* num = calloc(size, sizeof(int));
    num[0] = 1;
    num[1] = 1;
    printf("\n%d", sumOfDigits(num, n));
    free(num);
    return 0;
}

4

2 回答 2

1

似乎发布的代码正在使用“隐式”任意精度类型(“数字”在 [0, 999999999] 范围内)来递归计算所有乘以 2,这意味着,例如 n = 100,执行 100乘以那些膨胀的计算。

根据指数是偶数还是奇数,每次将数字乘以自身或乘以 2 应该更有效(O(log(n))而不是 O(n))。例如 2 7 = 2 * (2 3 * 2 3 )。

另一种方法是显式实现 Bing Int 类型,但使用二进制底层类型(例如 a uint32_t)。计算 2 n将是微不足道的,它只是一个最终幂为 2 的零数组(同样,只有一个非零位)。

现在,要获得(以 10 为底)数字的总和,您需要将该数字转换为基数,例如 100000000(就像 OP 所做的那样),为此,您必须在两个 Big Ints 和一个 long 之间实现一个长减法除以 100000000,这也会给你余数。使用该余数计算数字的部分总和并进行迭代。

以下是最小实现,可在此处测试。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>

#define D_BASE 1000000
#define MSB_MASK 1 << 31

typedef struct
{
    uint32_t size;
    uint32_t capacity;
    uint32_t *digits;
} BigInt;

void divide_bigint(BigInt *n, uint32_t x, uint32_t *remainder);

BigInt *make_bigint_of_two_raised_to(uint32_t n)
{
    BigInt *p = malloc(sizeof *p);
    if (!p)
    {
        perror("Fatal error");
        exit(1);
    }
    uint32_t pos = n / 32;
    uint32_t remainder = n % 32;
    uint32_t capacity = (remainder == 31) ? pos + 2 : pos + 1;
    uint32_t *pp = calloc(capacity, sizeof *pp);
    if (!pp)
    {
        perror("Error initializing a Big Int as a power of two");
        free(p);
        exit(1);       
    }
    p->capacity = capacity;
    p->size = capacity;
    pp[pos] = 1u << remainder;
    p->digits = pp;
    return p;
}

void free_bigint(BigInt **p);

uint64_t sum_of_digits_of_two_raised_to_the_power(uint32_t n)
{
    BigInt *power_of_two = make_bigint_of_two_raised_to(n);
    uint32_t remainder;
    uint64_t sum = 0;
    while (!(power_of_two->size == 1  &&  power_of_two->digits[0] == 0))
    {        
        divide_bigint(power_of_two, 1000000000, &remainder);
        while (remainder)
        {
            sum += remainder % 10;
            remainder /= 10;
        }
    }
    free_bigint(&power_of_two);
    return sum;
}

void test(uint32_t n)
{
    uint64_t sum = sum_of_digits_of_two_raised_to_the_power(n);
    printf("Sum of digits of 2^%d: %" PRIu64 "\n", n, sum);
}

int main(void)
{
    test(5);
    test(10);
    test(1000);
    test(10000);
    test(100000);
    test(1000000);
    return 0;
}

void shrink_size(BigInt *n)
{
    while ( n->size > 1 )
    {
        if ( n->digits[n->size - 1] == 0  &&  !(n->digits[n->size - 2] & MSB_MASK) )
            --n->size;
        else
            break;
    }
}

void divide_bigint(BigInt *n, uint32_t x, uint32_t *remainder)
{
    uint64_t carry = 0;
    uint32_t i = n->size;
    while ( i-- > 0 )
    {
        carry <<= 32;
        carry += n->digits[i];
        if ( carry < x )
        {
            n->digits[i] = 0;
            continue;
        }
        uint64_t multiplier = (carry / x);
        carry -= multiplier * x;
        n->digits[i] = (uint32_t)multiplier;
    }
    shrink_size(n);
    *remainder = carry;
}


void free_bigint(BigInt **p)
{
    if (p && *p)
    {
        free((*p)->digits);
        free(*p);
        *p = NULL;
    }
}
于 2019-12-01T13:02:02.040 回答
1

2^8 = (2 * 2 * 2 * 2 * 2 * 2 * 2 * 2) = (2 * 2 * 2 * 2) * (2 * 2 * 2 * 2) = (2 * 2 * 2 * 2 )^2 = ((2 * 2) * (2 * 2))^2 = ((2 * 2)^2)^2 = ((2^2)^2)^2

因此,首先您需要计算 log(2, n),以了解如何有效计算。如果 log(2, n) 是一个整数,那么您可以通过很少的操作简单地计算平方的平方的平方。如果 log(2, n) 不是整数,则计算 2^((int)log(2, n)),因此您将非常有效地进行部分计算,然后对余数进行相同的计算,直到不再有余。

将部分结果统一为一个数字(可能由数组表示)并计算数字的总和。计算数字的总和很简单。2^n 的实际计算是最耗时的。

如果您没有达到数字格式的限制,那么您可以考虑左移,但对于您使用的域,这并不是一个真正的选择。

于 2019-12-01T15:38:33.523 回答