最小可能组和的最大值

Smallest possible sum of groups highest values

本文关键字:和的 最大值      更新时间:2023-10-16

我有一个无序的随机正整数数组。(例如:{70年42岁,68年,35岁,1日,25日,79年,59岁,63年,65})

我想把这个数组分成更小的组。原数组中相邻的2个数字,不能在同一组中。(例如42 &68必须在不同的组)

每组最大数之和,应尽量小。(例如,{42,68,35,1,70,25,79,59,63,65}的最优组为{79,70,68,63},{65,59,42,35,25},{1},总和为79 + 65 + 1 = 145。

我当前的算法将值与索引配对,按值降序排序数组,循环遍历数组,检查相邻的索引是否在组1中找到,如果没有,插入值,否则转到下一组等等。

//std::vector<std::vector<int>> groups;
//std::vector<std::pair<int, int>> pairs; Sorted by first element (value)
//for each value in the array
for (int j = 0; j < n; j++)
{
bool notfound = false;
//for each group
for (int z = 0; z < groups.size(); z++)
{
//Check if the group doesn't have a value with index +/- 1 of current
if ( std::find(groups[z].begin(),groups[z].end(), pairs[j].second + 1) == groups[z].end()
&& std::find(groups[z].begin(), groups[z].end(), pairs[j].second - 1) == groups[z].end())
{
//if not found in the group, add it
groups[z].push_back(pairs[j].second);
notfound = true;
break;
}
}
//If found, create new group and add there
if (!notfound)
{
//First value of group is always it's biggest since
//we use array sorted by value, so the first number can be
//added to the sum
totalSum += pairs[j].first;
groups.push_back(std::vector<int>{pairs[j].second});
}
}

我知道这有点乱。但代码的问题是,它不是每次都给出最优和。(例如,使用上面的示例数组,此代码将为您提供组{79,70,68,65},{63,42,35,25},{59,1},和79 + 63 + 59 = 201> 145)

编辑:
算法是不工作的方式,我需要它。我正在寻找另一种方法来解决这个问题,或者对当前算法进行修改,该算法考虑了我的算法没有考虑的部分问题。

在数组中可能有多个相同数量的实例,所以我不认为std::set会工作。

我不会处理代码,但我会告诉你我的想法/算法,因为我更像一个数学家而不是一个程序员。

将所有数分组后,原数组中最大的数无疑也是该组中最大的数。(根据你的解释,我相信你也注意到了。)

带有符号的原始数组示例:

a1, a2, a3, a4, a5, a6, a7, a8, a9, a10

想象a7是最大的数。我们将重点关注与a7"应该"在同一组中的其他数字。这些数字应该尽可能大,以便其他组的数字更小,这样会减少结果的总和。考虑a7左边的元素:判断下列数字中哪个和最大:

1) a5 + a3 + a1

2) a1 + a2

3) a4 + a2

4) a4 + a1

编辑:我忽略了5)a3 + a1,因为1)将大于5)(如果没有元素为负)。但是,即使存在负元素,下面的子函数也会按照期望处理它,因为它会考虑所有选项的总和。

注意每行中的这些数字不是连续的。给出一个数组(在这个数组中是[a1, a2, a3, a4, a5]),写出这些选项(在这个数组中是1),2),3)和4),并计算它们的和,然后决定哪个和是最大的,最大的和属于哪个选项。

一旦你找到那个选项,将那个选项的数字与a7合并,并把它们放在第一组。对a7:

的右侧执行同样的操作。1) a9

a10 2)并将1)(a9)或2)(a10)也放入该组。从原始数组中删除所有已使用的元素,并像递归函数一样重复此过程。

一个更通用的例子:

originalArray=[a1, a2,…], ax, a(x+1),…一)

假设最大的元素是ax。

调用前面提到的子函数:

subFunction([a1, a2,…], a(x-2)]作为数组;//对于左侧

它应该返回一个数组,称之为array1。

subFunction([a(x+2), a(x+3),…], an]作为数组);//为右侧

它应该返回一个数组,称之为array2。

define: group1 = Union({ax}, elementsOf(array1), elementsOf(array2))

修改:originalArray.removeElements (elementsOf (group1))

对新的originalArray重复此过程,这次将输出放入group2。然后是第三组,第四组……直到originalArray为空…

我可以写一个visual basic代码,如果你想/需要帮助/例子。

三组就够了

我们称赋给组X的最高元素为highest(X)。

首先,请注意,无论何时你有一个包含两个组X和Y且最高(X) <=最高(Y)的解,如果你可以将X中的任何数字移到Y中而不违反不属于Y的相邻数字的约束,你将得到一个不差,甚至可能更好的解。

元素i的组被约束为不同于元素i-1的组,也不同于元素1+i的组。也就是说,最多有2个组是不允许的("最多"是因为元素i-1和i+1可能彼此在同一组中)。但它可以是任何其他组。假设我们有一个溶液S,它包含4个或更多基团,其中a、B和C分别是含有最高、第二和第三元素的基团。(实际上它们的相对顺序并不重要——重要的是它们中最高的元素至少和其他元素一样高。)然后,对于任意3个不同的组X, Y, Z,且A, B或C中的任何一个,在S中任意3个连续组分配的块中,我们可以改变中间元素的分配如下,不使使分数变差:

AXA => ABA or ACA
AXB => ACB
AXC => ABC
AXY => ABY or ACY
BXA => BCA
BXB => BAB or BCB
BXC => BAC
BXY => BAY or BCY
CXA => CBA
CXB => CAB
CXC => CAC or CAB
CXY => CBY or CAY
YXZ => YAZ or YBZ or YCZ

(当X出现在字符串的开头或结尾时,实际上还有一些情况需要处理,但它们不是很令人兴奋,所以我没有把它们写出来。)请注意,在每次替换中,唯一改变的是,赋值给X的单个元素现在被赋值给a、B或c中的一个。

因此,给定任何包含4个或更多组的解S,我们可以简单地遍历分配给非{A,B,C}组的每个元素,并使用上述转换之一将其分配给A,B或C。在对每个这样的元素重复此操作之后,我们现在得到的是一个解S'

  1. 只使用A、B、C三个组,
  2. 并不比原来的解决方案差,s

因为我们可以对任意解S执行这些步骤,这意味着我们唯一需要查看的解是使用最多3个组的解!:)

(注意,我们的算法不会以这种方式转换实际的具体解——上面只是一个证明的草图,我们不需要首先评估4种或更多颜色的解,因为它们不可能比最好的2或3组解更好。)

算法既然我们已经限制了组的数量,这个问题可以用动态规划来解决,尽管如何有效地细分它还不是很明显。

设v[1]为输入的第一个元素,v[2]为第二个元素,以此类推。同样,令v[0] = 0。(我们将使用这个特殊索引来描述使用少于3组的部分解。)

设f(i, j, k, p)是仅由前i个元素组成的子问题的最小和,在附加约束条件下,分配给A的最高元素在位置j,分配给B的最高元素在位置k,并且最右边的位置(即元素i)分配给p群(可以是A, B或C)。首先注意,这些参数足以确定所有3组中最高的元素:

highest(A) = v[j]
highest(B) = v[k]
highest(C) = f(i, j, k, P) - v[j] - v[k]

(从问题状态的描述中省略分配给C的最高元素,使我们可以得到O(n^3)的解决方案,而不是我们需要的O(n^4)的解决方案,尽管代价是使计算f()的情况有点不对称和奇怪。)在任何情况下,我们都可以这样计算f()(对于i, j, k和P的任何值,选择第一个匹配的行):

f(0, 0, 0, _) = 0
f(0, _, _, _) = infinity (can't assign anything to A or B when i=0)
f(i, i, i, _) = infinity (can't assign elem i to A and B)
f(i, i, k, A) = minimum of min(f(i-1, j, k, B), f(i-1, j, k, C)) - v[j] + v[i] over all 0 <= j < i and such that v[j] <= v[i]
f(i, j, i, A) = infinity (can't assign elem i to A if it's the highest in B)
f(i, j, k, A) = IF v[i] > v[j] THEN infinity ELSE min(f(i-1, j, k, B), f(i-1, j, k, C))
f(i, i, k, B) = infinity (can't assign elem i to B if it's the highest in A)
f(i, j, i, B) = minimum of min(f(i-1, j, k, A), f(i-1, j, k, C)) - v[k] + v[i] over all 0 <= k < i and such that v[k] <= v[i]
f(i, j, k, B) = IF v[i] > v[k] THEN infinity ELSE min(f(i-1, j, k, A), f(i-1, j, k, C))
f(i, i, k, C) = infinity (can't assign elem i to C if it's the highest in A)
f(i, j, i, C) = infinity (can't assign elem i to C if it's the highest in B)
f(i, j, k, C) = IF v[i] < min(f(i-1, j, k, A), f(i-1, j, k, B)) - v[j] - v[k] THEN infinity ELSE v[i] + v[j] + v[k]

原问题最高群元素的最小可能和是{A, B, C}中所有1 <= j, k <= n和p上f(n, j, k, p)的最小值。可以使用标准的DP回溯技术恢复实际的分区组(基本上,一个前导数组p[i][j][k][p]记录最小化任何"最小值"的j或k的值)。总的来说……"这允许恢复之前的状态)。

上述递归式可以直接实现为递归函数,但这将导致解时间呈指数级增长。为了得到一个多项式时间的解,我们可以简单地应用记忆——也就是说,我们可以记录已经解决的子问题的解值,而不是每次重新计算它们。

1 <= i, j, k <= n, p只能是3个不同组中的一个,因此有O(n^3)个可能的子问题需要解决。如果i, j, k都是不同的,那么计算f()需要O(1)时间;虽然当i = j或i = k时,我们需要O(n)时间来找到O(n)项的最小值,但这种情况只有O(n^2)个。因此整体时间复杂度为O(n^3). 这也是朴素实现的内存需求,尽管可以通过利用计算f(i,…)的事实将其降低到O(n^2),我们只需要访问f(i-1,…),所以我们只需要保持f()值为i的前一个值,而不是所有之前的值。

c++实现在充实了基本用例并修复了一个错误之后,我现在已经将上面的递归实现为一个c++程序。对于示例,它正确地输出145,尽管我没有实际实现记忆,但它只需要几毫秒。它还在我尝试过的其他几个小序列上给出了正确的输出。