首先,您应该确定要最小化的确切值。
让我们表示S 是数字的总和,而s1、s2、s3 和s4 是某个解决方案中四个部分的总和。
我们可以定义一个相当模糊的术语“尽可能平等”的许多精确表示。也就是说,max(s1,s2,s3,s4)-min(s1,s2,s3,s4) 是否应该尽可能小?或者max(|s1-S/4|, |s2-S/4|, |s3-S/4|, |s4-S/4|) 应该尽可能小?或者,比如说|s1-S/4|+|s2-S/4|+|s3-S/4|+|s4-S/4|?等等。
我可以为第二个指标想一个简单的解决方案:max(|s1-S/4|, |s2-S/4|, |s3-S/4|, |s4-S/4|) 被最小化。
首先,让我们解决一个不同的问题。给定您的序列和某个值 X,我们可以将其划分为 max(|s1-S/4|, |s2-S/4|, |s3-S/4|, |s4-S/4|)<=X 吗?如果我们可以为任意X解决这个问题,那么最初的问题是通过对X进行二分查找来解决的。
那么,我们如何检查是否存在带有max(|s1-S/4|, |s2-S/4|, |s3-S/4|, |s4-S/4|)<=X 的分区?这个要求等价于要求S/4-X<=s[i]<=S/4+X,所以对于每个块我们知道允许的最小和最大和。
现在从头开始计算当前总和并标记第一个块可以结束的位置 - 这将是从开始的总和从 S/4-X 到 S/4+X 的位置。
现在找到第二个块可以结束的位置。这有点棘手。最简单的方法是从第一个块的每个找到的结束位置开始,并找到第二个块的相应可能结束位置。但是有一种更快的方法。首先,从第一个chunk的第一个可能的结束位置开始,计算第二个chunk对应的结束位置。然后,移动到第一个块的第二个可能的结束位置。请注意,这只会为第二个块添加一些新的结束位置,这些位置位于已找到位置的右侧,因此无需全部重复;如果您保留“当前”第二个块所覆盖的累积跨度总和,那么您可以在O(N) 中找到第二个块的所有可能位置。所以你标记了第二个块的所有可能的结束位置。
类似地找到第三块和第四块的可能结束位置。如果数组的结尾在第四个块的可能结束位置中,则可以进行这样的划分,否则不可以。分割本身可以通过简单的方式恢复,我就不赘述了。
这样编码:
func check(a,S,X) // a is given array
// canEnd[i,j] is whether the i-th chunk can end just before position j :
// canEnd[i,j]==0 --- can not end
// canEnd[i,j]==1 --- can end
// cadEnd[i,j]==2 --- can end and this is the final possible position
fill canEnd with zeroes
canEnd[0,0] = 2
l = 0 // left end of 'current' chunk
r = 0 // right end of 'current' chunk (not inclusive)
curs = 0 // sum of the 'current' chunk
for i = 1..4
while true
last = -1
while curs <= S/4+X
if curS > S/4-X
canEnd[i,r] = 1
last = r
s +=a[r]
r++
// now processed all chunks that start at l
if canEnd[i-1,l] == 2
canEnd[i,last] = 2
break
do
s -= a[l]
l++
until canEnd[i-1,l]>0
// main code
left = -1
right = S
while right - left > 1
middle = (right + left) /2
if can(middle)
right = middle
else left = middle
// the answer is right
(请注意,我没有测试代码,很可能它包含错误,此处仅用于说明目的。)
对于max(s1,s2,s3,s4)-min(s1,s2,s3,s4) 度量,可以应用类似的方法,但您必须首先从 0 迭代到 S/4 以尝试 min(s1,s2,s3,s4) 的每个可能值。对于min(s1,s2,s3,s4) 的每个可能值,对最大可能值进行二分搜索,您再次为每个s[i] 定义了范围。