寻找三个整数,使它们的余弦值之和最大。

17

给定三个整数 x, yz(每个都 >= 1),以及一个给定的上限整数 n < 10^6。 同时,n = x + y + zoutput = cos(x) + cos(y) + cos(z)

练习是最大化 output

我为此编写了一个简单的脚本,但时间复杂度为 O(n^3)。 有没有简化这个问题的方法?

from math import cos

n = 50
x = 1
y = 1
z = 1

total = cos(x) + cos(y) + cos(z)

for x in xrange(n):
    for y in xrange(n):
        for z in xrange(n):
            if x + y + z == n:
                temp = cos(x) + cos(y) + cos(z)
                if temp > total: total = temp

print round(total, 9) 

1
查找背包算法。基本上,难点在于避免测试 x+y+z 是否匹配 n,因为它们太大了。 - Jean-François Fabre
动态规划算法涉及计算部分和,对结果进行排序,并使用二分法来找到答案。余弦函数的引入使结果具有一定的“随机性”。这是一个类似于“项目欧拉”的有趣问题。 - Jean-François Fabre
我认为你的意思不是“简化”,而是“让它运行更快”。 - gnasher729
“x”,“y”和“z”的正确结果是什么? - iGian
你的意思是三个“不重复”的整数吗? - MSalters
5个回答

13

正如Jean-François Fabre在评论中指出的那样,您可以应用许多技巧来提高性能,但首先需要注意:

  • 注意到ab的值确定了c的值,
  • 请注意三个变量中至少有一个(不失一般性地设为a)小于或等于N/3
  • 利用bc中剩余的对称性将b限制在a(N - a)//2 + 1之间
  • 预计算所有相关的cos值,并尝试避免连续快速查找相同的值,
  • 修剪外循环以在给定的cos(a)值永远无法导致新的最大值时及早停止,
  • 使用Numba进行JIT编译,从而获得额外的性能(对于N = 500大约为400倍),

然后,否则粗暴的解决方案会相对较快地结束,即使对于N = 1000000也是如此:

import numpy as np
from numba import jit

@jit
def maximize(N):
    cos = np.cos(np.arange(N))
    m = -3
    for a in range(1, N//3 + 1):
        cosa = cos[a]
        if m - 2 > cosa:
            continue
        for b in range(a, (N - a)//2 + 1):
            c = N - a - b
            res = cosa + cos[b] + cos[c]
            if res > m:
                m = res
                bestabc = (a, b, c)
    return m, bestabc

maximize(1000000)  # (2.9787165245899025, (159775, 263768, 576457))
值得注意的是,上面利用的对称性只适用于愿意忽略数值问题造成的浮点数加法通常不满足交换律的情况;也就是说,cos(a) + cos(b) 不一定等同于 cos(b) + cos(a)。尽管如此,您可能不必担心这个问题。

7
理想情况下,您希望仅计算每个可能的组合一次。 忽略cos的几何属性,将其视为仅仅是从数字到数字的某种映射(例如将其用作随机属性,如@Jean在他的第二条评论中提到的)。 首先,您必须意识到,在选择了2个数字之后,第三个数字已经确定了。您可以“聪明地”选择以避免冗余的选择:
from math import cos
import time
import numpy as np
from numba import jit



def calc(n):
    x = 1
    y = 1
    z = 1
    total = cos(x) + cos(y) + cos(z)
    for x in range(n, int((n/3 - 1)),-1): #I only want to pick X from n-2 to  n/3 -1 , after that we will repeat.
        cosx = cos(x)
        for y in range(max(int(((n-x)/2))-1,1),min(int(n-x),int(n/3))): #I would only pick number that will not be choosen for the z
                z = n-x-y #Infer the z, taking the rest in account
                temp = cosx + cos(y) + cos(z)
                if temp > total: total = temp
    return total

tic = time.clock()
total = calc(10000)
print(time.clock()-tic)

print (total)

在我的电脑上需要 1.3467099999999999 的时间(可能执行该操作)。 如 @fuglede 所述,值得使用 numba 进行进一步优化。

编辑: 实际上保存所有先前计算的余弦值比重新计算它们更昂贵。当你访问 np 数组时,你不仅仅是访问内存中的一个点,而是使用 ndarray 函数。使用 Python 内置的 cos 实际上更快:

import numpy as np

from math import cos
import time
import timeit

cos_arr = np.cos(np.arange(10000000))
tic = time.time()

def calc1():
    total = 0
    for j in range(100):
        for i in range(10000000):
            total += cos_arr[i]

def calc2():
    total = 0
    for j in range(100):
        for i in range(10000000):
            total += cos(i)

time1 = timeit.Timer(calc1).timeit(number=1)

time2 = timeit.Timer(calc2).timeit(number=1)
print(time1)
print(time2)

输出结果:

127.9849290860002
108.21062094399986

如果我将数组的创建放在计时器内部,速度会更慢。

任何一个答案如果对于每个 x 调用了超过一次 cos(x),都应该被点踩。 - TonyK
Python内置的cos函数比np数组访问更快,所以你是错的。添加了代码来证明它。 - Dinari
你不需要使用数组访问。在第一个for语句之后,只需设置cosx = cos(x),然后使用它代替cos(x)即可。试一试看看。 - TonyK
这确实有所帮助。但是我没有机器来进行 JIT 编译,以便比较它们在 N = 10^6 的情况下的差异。 - fuglede
将其运行在10000到100000之间的各种值上似乎是一致的;考虑到双重引用,这也有一定道理。 - fuglede
显示剩余6条评论

3
完全没有必要计算3 x n^3余弦值。
我们可以假设x ≤ y ≤ z。因此,x可以是1到n/3范围内的任何整数,y可以是x到(n-x)/2范围内的任何整数。而z必须等于n - x - y。这样就将你尝试的三元组(x, y, z)数量从n^3减少到约n^2 / 6。
接下来假设你找到了三个数字,总和为2.749。并且你尝试了一个cosine(x)=0.748的x。任何涉及此x的总和都不能超过2.748,所以你可以直接拒绝x。一旦你找到一个好的总和,你就可以拒绝许多x的值。
为了使这更加有效,你可以按cosine(x)的最高到最低值对x值进行排序,因为这样更有可能找到一个高的总和,从而可以删除更多的值。
由于计算cos(x)很慢,所以你可以将值存储在表中。
Set c[i] = cos (i) for 1 ≤ i ≤ n. 
Set x[i] = integers 1 to n/3, sorted in descending order by value of c[i]. 
Set (bestx, besty, bestz) = (1, 1, n-2) and total = c[bestx] + c [besty] + c [bestz].

for x = elements of array x where c[x] + 2 ≥ bestTotal
    for y = x to (n-x)/2
        z = n - x - y
        total = c[x] + c[]y] + c[z]
        if total > bestTotal
            (bestx, besty, bestz) = (x, y, z)
            bestTotal = total

如果y + z的和是固定的,比如这里y + z = n - x,你可以通过一些数学方法来改进。cos(y) + cos(z)的和是有限制的。设P为最接近(n - x) / 2pi的整数,d = (n - x) - P * 2pi,则cos(y) + cos(z)的最大可能和是2 * cos(d/2)。
因此,对于每个x,1 ≤ x ≤ n/3,我们计算出该值d和cos(x) + 2 * cos(d/2),将这些值作为可以通过某些x实现的最大总和进行存储,并按降序排序x,忽略那些可实现总和小于迄今为止最佳总和的x。
如果n非常大(比如十亿),则可以使用欧几里得算法快速找到所有接近2k*pi + d的整数y,但这会有点复杂。
for x in 1 to n/3
    let s = n - x
    let P = s / 2pi, rounded to the nearest integer
    let d = (s - P * 2pi) / 2
    let maxSum [x] = cos(x) + 2*cos(d)

Set x[i] = integers 1 to n/3, sorted in descending order by value of maxSum[i]. 
Set (bestx, besty, bestz) = (1, 1, n-2)
Set bestTotal = c[bestx] + c [besty] + c [bestz].

for x = elements of array x where maxSum[x] ≥ bestTotal
    for y = x to (n-x)/2
        z = n - x - y
        total = c[x] + c[]y] + c[z]
        if total > bestTotal
            (bestx, besty, bestz) = (x, y, z)
            bestTotal = total

PS. 实际上,我尝试了一些N值约为1亿的情况。结果发现,我可以将数组排序,以尝试首先尝试最有前途的x值,这需要很长时间,但通常只会尝试第一个x值。或者我可以使用x = 1、2、3等,这意味着将尝试几十个x值,这比排序更快。


"z 必须等于 n - x - y。" 这是正确的,但 n 大多数情况下是未知的(只受限于 <1,000,000)。 - MSalters

1
不需要计算余弦即可回答这个问题。当n从1到N(N为搜索的上限)时,只需跟踪函数f(n) = abs(2pi*n-round(2pi*n))的三个最小值(如果允许n=0,则为两个最小值)。
余弦在2*pi的倍数处为1,因此我们搜索最接近整数的两个或三个倍数,且在搜索范围内。
我还没有运行程序来完成这个任务,但是在任何编程语言中都应该很容易。我将使用Mathematica。

-1

这纯粹是一个基本的三角学问题。你的方程的最大值将在每个余弦函数的值都为1时达到。对于cos(n),其中n是任意数字,在由n = 2 * pi * k组成的k >= 0且k为整数的所有值中,你的余弦函数将具有值1。你的x、y和z值属于这个集合,并且这些值的排列将使你得到所需的值。 此外,不要忘记检查集合中的n是否为整数,以减少样本空间。


2
这样的数字将是无理数,而@barbossa的所有输入都是有理数。 - fuglede
据我所知,0是一个有理数,因此问题的复杂度现在降低到了O(1)。我编辑了我的答案。 - S. Patel
1
输入被假定为正数。 - fuglede
@S.Patel 因为 x、y 和 z 都是正整数,所以没有有限的 n 可以给你余弦值为 1 的解。 - Sneftel
请仔细阅读问题。当n = 3时,您唯一的选择是x = y = z = 1,总和为3 * cos(1)≈ 1.62。当n = 5时,最佳解是x = y = 1,z = 3,总和约为0.09。 - gnasher729

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接