如何找到具有从1到n以及从n + 1到m的总和相同的几对数字

时间:2018-09-17 20:19:43

标签: c

所以,我必须为大学做一份工作,其中包括创建一个算法。

算法必须找到几个满足一定条件的数字,即:1n的总和(排他性)的结果与n+1至{ {1}}(含)。

最后,算法必须给出至少15对。

第一对是m6,因为从81(不包括)(6)是n = 1+2+3+4+515n+1m = 8+7

我创建的算法如下:

15

这实际上是可以的,但是就复杂度而言,某些输出是不正确的,并且它也很糟糕,并且由于我正在研究算法的复杂度,因此最好找到一种比此算法更好的算法。 / p>

我也尝试使用Java进行此操作,但是使用int不能很好地解决此问题,而使用long则需要花费数小时才能计算。

到目前为止我发现的数字:

int main() {
    int count = 0;
    unsigned int before = 0;
    unsigned int after = 0;
    unsigned int n = 1;
    unsigned int m = 0;
    do {
        before += n - 1;
        after = n + 1;
        for (m = after + 1; after < before; m++) {
            after += m;
        }
        if (before == after) {
            printf("%d\t%d\n", n, (m - 1));
            count++;
        }
        n++;
    } while (count < 15);
}

以下内容可能不正确:

6 and 8  
35 and 49  
204 and 288   
1189 and 1681  
6930 and 9800  
40391 and 57121  

2 个答案:

答案 0 :(得分:7)

超出前6个的结果不正确:类型unsigned int的范围不足以存储和。您应该为unsigned long longbefore使用类型after

此外,对于大值,您的算法会变得非常慢,因为您需要为每个after的新值从头重新计算before,时间复杂度为 O(N 2 < / sup>)。您可以并行保存2个运行总和,并将复杂度降低为准线性。

最后但并非最不重要的是,UINT32_MAX下只有12个解决方案,因此请输入unsigned long long,该字段必须保证n和{{1 }}。为避免错误的结果,更新m时应测试溢出。

进一步的测试表明,after附近的after的值beforem的和超过64位。一种解决方案是在8589934591before都达到2 63 时从2中减去2 62 。通过这种修改,程序可以继续搜索更大的aftern值,而超过32位。

这是改进版:

m

输出(运行时间30分钟):

#include <stdio.h>

int main() {
    int count = 0;
    unsigned long long n = 1;
    unsigned long long m = 2;
    unsigned long long before = 0;
    unsigned long long after = 2;

    for (;;) {
        if (before < after) {
            before += n;
            n++;
            after -= n;
        } else {
            m++;
            /* reduce values to prevent overflow */
            if (after > 0x8000000000000000) {
                after -= 0x4000000000000000;
                before -= 0x4000000000000000;
            }
            after += m;
            while (before > after) {
                after += n;
                n--;
                before -= n;
            }
        }
        if (before == after) {
            printf("%llu\t%llu\n", n, m);
            count++;
            if (count == 15)
                break;
        }
    }
    printf("%d solutions up to %llu\n", count, m);
    return 0;
}

答案 1 :(得分:2)

您上面发布的初始暴力程序会生成大量数据供您分析。问题中的人们建议使用“算术级数之和”公式,而不要重复添加,但事实是,它仍然会运行缓慢。当然,这是一种改进,但是如果您想使用某些有用的东西,还不够好。

信不信由你,nm的值有一些模式,这需要一些数学解释。我将使用函数n(i)m(i)d(i) = m(i) - n(i)分别表示nm的值以及它们之间的区别,在迭代i期间。

您找到了前六对夫妇:

 i   n(i)   m(i)   d(i)
== ====== ====== ======
 1      6      8      2
 2     35     49     14
 3    204    288     84
 4   1189   1681    492
 5   6930   9800   2870
 6  40391  57121  16730

请注意6+8 = 1435+49 = 84204+288 = 492等。通常,d(i+1) = m(i) + n(i)(例如d(2) = m(1) + n(1) = 6 + 8 = 14)就发生了。 / p>

现在我们知道以下内容:

  d(7)
= n(6) + m(6)
= 40391 + 57121
= 97512

# m(i) = n(i) + d(i)
m(7) = n(7) + 97512

m(i) = n(i) + d(i)开始的另一种查看方法是d(i+1) = d(i) + 2n(i)

  d(7)
= n(6) + d(6) + n(6)
= d(6) + 2n(6)
= 16730 + 2(40391)
= 97512

d(i)也对计算n(i+1)有用:

n(i+1) = 2d(i+1) + n(i) + 1

n(7) = 2d(7) + n(6) + 1
    = 2(97512) + 40391 + 1
    = 235416

从那里很容易确定事情:

 i   n(i)   m(i)   d(i)
== ====== ====== ======
1       6      2      8
2      35     14     49
3     204     84    288
4    1189    492   1681
5    6930   2870   9800
6   40391  16370  57121
7  235416 332928  97512

那开始条件呢?我们需要一种首先找到6的方法,并且可以通过向后工作并使用替换来计算起始情况:

n(1) = 2d(1) + n(0) + 1
   6 = 2(2) + n(0) + 1
   5 = 4 + n(0)
   1 = n(0)

d(1) = d(0) + 2n(0)
   2 = d(0) + 2(1)
   2 = d(0) + 2
   0 = d(0)

m(0) = n(0) + d(0)
     = 1 + 0
     = 1

请注意,n(0) = m(0)1 = 1)并不是一对。要使一对数字成为一对,数字不能相同。

剩下的就是计算总和。由于从1到n-1的整数(即1到n,不包括n)形成算术级数并且该级数从1开始,因此您可以使用公式

       n(n - 1)
S(n) = --------
          2

下面是一个使用所有这些信息的程序。您会注意到我正在使用乘法函数mul代替乘法运算符。当遇到无符号溢出(即环绕)时,该函数的结果用于过早结束循环。可能有更好的方法来检测环绕行为,并且可以对算法进行更好的设计,但它可以工作。

#include <errno.h>
#include <limits.h>
#include <stdio.h>

typedef unsigned long long uval_t;

/*
 * Uses a version of the "FOIL method" to multiply two numbers.
 * If overflow occurs, 0 is returned, and errno is ERANGE.
 * Otherwise, no overflow occurs, and the product m*n is returned.
 */
uval_t mul(uval_t m, uval_t n)
{
/*
 * Shift amount is half the number of bits in uval_t.
 * This allows us to work with the upper and lower halves.
 * If the upper half of F is not zero, overflow occurs and zero is returned.
 * If the upper half of (O+I << half_shift) + L is not zero,
 * overflow occurs and zero is returned.
 * Otherwise, the returned value is the mathematically accurate result of m*n.
 */
#define half_shift ((sizeof (uval_t) * CHAR_BIT) >> 1)
#define rsh(v) ((v) >> half_shift)
#define lsh(v) ((v) << half_shift)
    uval_t a[2], b[2];
    uval_t f, o, i, l;

    a[0] = rsh(m);
    a[1] = m & ~lsh(a[0]);
    b[0] = rsh(n);
    b[1] = n & ~lsh(b[0]);

    f = a[0] * b[0];
    if (f != 0)
    {
        errno = ERANGE;
        return 0;
    }

    o = a[0] * b[1];
    i = a[1] * b[0];
    l = a[1] * b[1];

    if (rsh(o+i + rsh(l)) != 0)
    {
        errno = ERANGE;
        return 0;
    }

    return lsh(o+i) + l;
}

int main(void)
{
    int i;
    uval_t n = 1, d = 0;
    uval_t sum = 0;
#define MAX 15

    for (i = 1; i <= MAX; i++)
    {
        d += n * 2;
        n += d * 2 + 1;
        sum = mul(n, n - 1) / 2;
        if (sum == 0)
            break;
        printf("%2d\t%20llu\t%20llu\t%20llu\n", i, n, n+d, sum);
    }

    return 0;
}

这将产生12行输出,最后一行是:

12            1583407981              2239277041     1253590416355544190

当然,如果您不关心总和,则可以完全避免对它们进行计算,并且可以找到全部15对夫妇,而无需检查64位类型的溢出。

要进一步求和,可以按照建议的顺序(从最不推荐到最不推荐):

  • 使用“ bignum”库,例如GNU MP,它类似于Java的java.math.BigInteger类,并且具有自己的类似printf的用于显示值的函数;如果您使用的是Linux,则它可能已经可用
  • 使用编译器的128位类型(假定它可用),并在必要时为其创建自己的打印功能
  • 创建您自己的“大整数”类型,并为其关联必要的加,减,乘,除等打印功能;一种易于打印的方式是,可以将两个unsigned long long值粘贴在一起,一个值代表较低的19个十进制数字(即,其最大值为999 9999 9999 9999 9999),另一个代表前19位数字总计38位数字,即10 38 -1或127位

但是,所需的全部15个总和并不适合64位,这使我担心问题的措词可能不正确,并且想要的东西与您写的有所不同。

编辑

要证明此方法有效,我们必须首先建立一些规则:

  • 对于任何值nm0 ≤ n < m必须为true,表示n == m被禁止(否则我们没有一对,也就是“有序对” )。
  • nm都必须是整数。

考虑到这一点,请考虑一种算法,用于计算一个算术级数的总和,该算术级数的开始于a,结束于b,每个连续项之间的差为+1 :

          (b - a + 1)(b + a)
S(a, b) = ------------------
                  2

          b² - a² + b + a
        = ---------------
                2

          b(1 + b) + a(1 - a)
        = -------------------
                   2

如果这样的系列始于a=1,则可以得出一个更简单的公式:

        b(b + 1)
 S(b) = --------
           2

将其应用于您的问题,您想知道如何找到满足以下条件的值:

 S(n-1) = S(n+1, m)

应用参数后,结果如下:

  (n-1)n   m(1 + m) + (n+1)[1 - (n+1)]
  ------ = ---------------------------
     2                2

  (n-1)n = m(1 + m) + (n+1)(1 - n - 1)

  n² - n = m² + m + (n+1)(-n)

  n² - n = m² + m - n² - n

 2n²     = m² + m

虽然对我的目的并不重要,但值得注意的是m² + m可以重写为m(m+1),而2n²表示m和{ {1}}必须被2整除。另外,由于至少一个表达式必须被2整除,因此一个必须是一个完美的正方形,而另一个必须是一个完美的正方形的两倍。换句话说,{{1 }}。您可以使用m+12n² = m(m+1) = 2x²y²找到另一个同等有效的解决方案,以生成xy的值,但是在此我将不做演示。

给出nmn(i+1)的等式:

m(i+1)

开始条件:

d(i+1)

我们可以通过在所有情况下用 d(i+1) = d(i) + 2n(i) = m(i) + n(i) n(i+1) = 2d(i+1) + n(i) + 1 = 2m(i) + 3n(i) + 1 m(i+1) = d(i+1) + n(i+1) = 3m(i) + 4n(i) + 1 代替n(0) = 1 d(0) = 0 m(0) = 1 来确定它们是否真正起作用,并确定是否得出相同的方程式。假设i+2i,方程f(n(i)) = 2n²(i)简化为g(m(i)) = m(i) ⋅ (m(i) + 1),证明方程对任何一对都有效:

f(n(i+2)) = g(m(i+2))

如果您迷失了方向,我只需从等式两边减去f(n(i)) = g(m(i)),得出 f(n(i+2)) = g(m(i+2)) f(2m(i+1) + 3n(i+1) + 1) = g((3m(i+1) + 4n(i+1) + 1)) 2 ⋅ (12m(i) + 17n(i) + 6)² = (17m(i) + 24n(i) + 8) ⋅ (17m(i) + 24n(i) + 8 + 1) 2 ⋅ (144m²(i) + 408m(i)⋅n(i) + 144m(i) + 289n²(i) + 204n(i) + 36) = 289m²(i) + 816m(i)⋅n(i) + 289m(i) + 576n²(i) + 408n(i) + 72 288m²(i) + 816m(i)⋅n(i) + 288m(i) + 578n²(i) + 408n(i) + 72 = 289m²(i) + 816m(i)⋅n(i) + 289m(i) + 576n²(i) + 408n(i) + 72 2n²(i) = m²(i) + m(i) f(n(i)) = g(m(i))

相关问题