所以,我必须为大学做一份工作,其中包括创建一个算法。
算法必须找到几个满足一定条件的数字,即:1
至n
的总和(排他性)的结果与n+1
至{ {1}}(含)。
最后,算法必须给出至少15对。
第一对是m
和6
,因为从8
到1
(不包括)(6)是n
= 1+2+3+4+5
从15
到n+1
是m
= 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
答案 0 :(得分:7)
超出前6个的结果不正确:类型unsigned int
的范围不足以存储和。您应该为unsigned long long
和before
使用类型after
。
此外,对于大值,您的算法会变得非常慢,因为您需要为每个after
的新值从头重新计算before
,时间复杂度为 O(N 2 < / sup>)。您可以并行保存2个运行总和,并将复杂度降低为准线性。
最后但并非最不重要的是,UINT32_MAX
下只有12个解决方案,因此请输入unsigned long long
,该字段必须保证n
和{{1 }}。为避免错误的结果,更新m
时应测试溢出。
进一步的测试表明,after
附近的after
的值before
和m
的和超过64位。一种解决方案是在8589934591
和before
都达到2 63 时从2中减去2 62 。通过这种修改,程序可以继续搜索更大的after
和n
值,而超过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)
您上面发布的初始暴力程序会生成大量数据供您分析。问题中的人们建议使用“算术级数之和”公式,而不要重复添加,但事实是,它仍然会运行缓慢。当然,这是一种改进,但是如果您想使用某些有用的东西,还不够好。
信不信由你,n
和m
的值有一些模式,这需要一些数学解释。我将使用函数n(i)
,m(i)
和d(i) = m(i) - n(i)
分别表示n
,m
的值以及它们之间的区别,在迭代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 = 14
,35+49 = 84
,204+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位类型的溢出。
要进一步求和,可以按照建议的顺序(从最不推荐到最不推荐):
java.math.BigInteger
类,并且具有自己的类似printf
的用于显示值的函数;如果您使用的是Linux,则它可能已经可用unsigned long long
值粘贴在一起,一个值代表较低的19个十进制数字(即,其最大值为999 9999 9999 9999 9999),另一个代表前19位数字总计38位数字,即10 38 -1或127位但是,所需的全部15个总和并不适合64位,这使我担心问题的措词可能不正确,并且想要的东西与您写的有所不同。
编辑
要证明此方法有效,我们必须首先建立一些规则:
n
和m
,0 ≤ n < m
必须为true,表示n == m
被禁止(否则我们没有一对,也就是“有序对” )。n
和m
都必须是整数。考虑到这一点,请考虑一种算法,用于计算一个算术级数的总和,该算术级数的开始于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+1
和2n² = m(m+1) = 2x²y²
找到另一个同等有效的解决方案,以生成x
和y
的值,但是在此我将不做演示。
给出n
,m
和n(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+2
和i
,方程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))
。