找到三个整数,使得它们的余弦值之和变为最大值

Posted

技术标签:

【中文标题】找到三个整数,使得它们的余弦值之和变为最大值【英文标题】:Finding three integers such that their sum of cosine values become max 【发布时间】:2019-05-03 07:33:12 【问题描述】:

共有三个整数xyz(每个都>= 1)和一个给定的上界整数n n = x + y + z 和 output = 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) 

【问题讨论】:

查找背包算法。基本上困难的部分是避免测试 x+y+z 如果你确定它们不会匹配 n 因为太大了。 动态规划算法,涉及计算部分和、排序结果和二分算法以找到答案。余弦是为了在结果中引入一些“随机性”。有趣的“欧拉项目”问题 我认为您的意思不是“简化”而是“让它运行得更快”。 xyz 的正确结果是什么? 大概你的意思是三个 unique 整数? 【参考方案1】:

正如 Jean-François Fabre 在 cmets 中指出的那样,您可以应用很多技巧来提高性能,但首先

注意到ab 的值决定了c 的值, 请注意,三个变量 WLOG 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) 相同。不过,您可能不会担心这一点。

【讨论】:

【参考方案2】:

理想情况下,您只想计算每个可能的组合一次。忽略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 进行进一步优化。

编辑: 保存所有先前计算的 cos 值实际上比重新计算它们更昂贵,当您访问 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) 的任何答案都值得一票否决。 Python 内置 cos 比 np 数组访问更快,所以你实际上是错的。添加了代码来演示它。 您不需要数组访问。您只需在第一个 for 语句之后设置 cosx = cos(x)`,并使用它而不是 cos(x)。试试看。 是的,你是对的,对你的意思有一些偏见,所以给你的评论错误的意思。我的目标只是减少 O,两者(重新评估和保存)都是 O(1),但你是正确的,它是多余的,修复它。 我无法重现您提到的改进:使用 N = 10^5 进行测试并使用 IPython %timeit 魔术,在内部循环中查找 cos[a] 而不是在外部查找稍微 更快。同时,不预先计算 cos 的值会使事情大大变慢:在我的机器上,N = 10^4,预先计算的版本需要 1.55 秒来执行,而每次计算它们需要 16.4 ms,而在内循环中计算 cos 需要 388 ms。两者之间的比率随着 N 的增加而增加。您的测试用例可能只有两个小才能正确捕捉查找的好处。【参考方案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。您尝试使用余弦 (x) = 0.748 的 x。任何涉及此 x 的总数都不能超过 2.748,因此您可以完全拒绝 x。一旦你找到一个好的总和,你就可以拒绝 x 的许多值。

为了使这更有效,您将值 x 从 cosine(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

附言。我实际上尝试了一些大约 1 亿的 N 值。事实证明,我可以对数组进行排序以首先尝试最有希望的 x 值,这需要很长时间,但通常 x 的第一个值是唯一被尝试的值。或者我可以使用 x = 1、2、3 等,这意味着将尝试几十个 x 值,这比排序更快。

【讨论】:

"z 必须等于 n - x - y。"。是的,但 n 大部分是未知的(仅限于 【参考方案4】:

不需要计算余弦来回答这个问题。只需跟踪函数的三个(如果允许 n=0,则为两个)最小值 f(n) = abs(2pi*n-round(2pi*n)) 因为n 从 1 变为 N,其中 N 是您的搜索上限。

余弦在2*pi 的倍数处为 1,因此我们在搜索范围内搜索最接近整数的两个或三个倍数。

尚未运行程序来执行此操作,但在任何编程语言中都应该很容易。我将使用 Mathematica。

【讨论】:

【参考方案5】:

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

【讨论】:

这样的数字是不合理的,而@barbossa 的所有输入都是合理的。 据我所知,0 是一个有理数,因此问题的复杂性现在降低到 O(1)。我编辑了我的答案。 假设输入为正数。 @S.Patel 因为 x、y 和 z 是正整数,所以对于有限的 n 没有解决方案,它的余弦为 1。 请仔细阅读问题。设 n = 3,那么你唯一的选择是 x = y = z = 1,总和是 3 * cos(1) ≈ 1.62。设 n = 5,最好的解是 x = y = 1,z = 3,总共约为 0.09。

以上是关于找到三个整数,使得它们的余弦值之和变为最大值的主要内容,如果未能解决你的问题,请参考以下文章

生日礼物(固定个数的连续区间最大值

CSU OJ1960

3493. 最大的和

《算法竞赛进阶指南》0.6倍增

返回整数数组最大子数组之和

求这十个中位数之和的最大值与最小值