最近,我参加了编程比赛。有一个问题,我仍然在考虑。编程语言并不重要,但我已经用C ++编写了它。任务是:
如您所知,Flatland位于飞机上。有n Flatland的城市,这些城市中的第i个位于(xi, YI)。居住在第i个城市的 ai 公民。国王 Flatland决定将王国分配给他的两个儿子。他 想要以无限直线的形式建造一堵墙;每个 部分将由其中一个儿子统治。墙不能通过 通过任何城市。为了避免兄弟之间的嫉妒,人口 两部分必须尽可能接近;正式地,如果 a 和 b 是 居住在城市中的第一个和第二个城市的公民总数 第二部分, | a - b | 的值必须最小化。 帮助国王找到最佳分工。城市数量较少 超过1000.所有坐标都是整数。算法输出 应该是最小的 | a-b |
的整数
好的,如果我知道线的方向,那将是非常简单的任务 - 二分法搜索:
我不想要代码,我想要点子,因为我没有。如果我抓住了想法,我可以编写代码!
我不知道最佳方向,但我认为可以通过某种方式找到它。那可以找到它还是以其他方式解决这个问题?
水平/垂直线不是最佳的示例:
1
\
\
2 \ 1
答案 0 :(得分:9)
Ansatz
蛮力方法是检查所有可能的分裂...
首先应该注意,线的确切方向无关紧要。它总是可以少量移动,并且有一些案例的最小值不止一个。什么重要的城市到哪个城市的哪个方面。即使只是尝试所有这些可能的组合,找到它们也并非易事。为此,我提出以下算法:
如何找到所有可能的部门
对于每对城市x和y,连接它们的线将王国划分为" left"和#34;对"。然后考虑左,右,x和y的所有可能组合:
left + x + y
vs right
(C)left + x
vs right + y
(A)left + y
vs right + x
(D)left
vs right + x + y
(B)实际上我不是100%肯定,但我认为通过这种方式你可以找到所有可能的分裂,只有有限数量的试验。由于城市没有大小(我假设半径为0),连接x和y的线可以稍微移动,以包括在其中任何一侧的任一城市。
这个简单方法肯定会失败的一个反例是超过2个城市在一条直线上
示例
这张照片说明了我的上述算法的一步来自OP的例子。 x和y是拥有1个居民的两个城市。实际上,在这对城市中,已经有了所有可能的分歧。 (无论如何,3分是微不足道的,因为对于什么组合是没有几何限制的。有趣的是,从4点开始,它们在飞机上的位置确实很重要。)
共线点
经过一些讨论和富有成效的评论,我得出的结论是,共线点不是真正的问题。在评估4个可能的划分(每对点)时,只需要考虑这些要点。例如。假设在上面的例子中是另一个点(-1,2)。然后,这一点将位于A和C的左侧,B和D的右侧。
答案 1 :(得分:4)
对于每个角度A
,考虑与x轴成A
角的平行线族,特殊情况A=0
对应于平行于X轴。
鉴于A
,您可以使用二元搜索来查找族中最接近王国的行。因此,从角度到整数,我们有一个函数f
,将每个角度A
映射到与|a-b|
对应的系列中的行的最小值A
。
我们需要尝试多少角度?只有当A
是一个与两点之间的直线相对应的角度时,情况才会发生重大变化,这个角度我称之为“跳跃角度”#34;。该功能是连续的,因此是恒定的,远离跳跃角度。我们必须尝试跳跃角度,其中约有n choose 2
,最多约为500,000。我们还必须尝试跳跃角度之间的角度间隔,将尺寸加倍,最多为1,000,000。
使用斜坡可能更明智,而不是角度。我只是想从角度来思考。
此方法的时间复杂度为O(n^2 log n)
,n^2
表示角度数,log n
表示二进制搜索。如果我们可以了解有关函数f
的更多信息,则可以使用更快的方法来最小化f
,而不是检查每种可能性。例如,在不等于跳跃角度的角度找到f
的最小值似乎是合理的。
也可以使用城市的质心来消除二元搜索。我们计算加权平均值
(a1(x1,y1) + a2(x2,y2) + ... + an(xn,yn))/(a1+a2+...+an)
我认为平衡人口的线路将通过这一点。 (嗯。)如果是这样,我们只需考虑角度。
答案 2 :(得分:2)
3
基本情况是有两个城市:在这种情况下,您可以简单地在连接两个城市的线上采用垂直线。
您可以通过拍摄每对两个城市来离散切线,并查看将它们连接为无限线方向的线条。
为什么会这样?
如果将城市数量分成两部分,则至少有一半有两个或更多城市。对于那部分,有两个点最接近边界。边界是否“非常接近”通过该线或具有相同的线并不重要;因为“略有不同的切线”不会交换任何城市(否则这些城市不是最接近的)。由于我们尝试“每个边界”,我们最终将生成具有给定切线的边界。
示例:强>
假设您有以下情况:
1
\
2\ 1
数字显示值。在这种情况下,边界处的两个最近点是顶部和右侧的点。因此,我们构建了一条向下45度的线。现在我们使用二分搜索来找到最佳分割:我们旋转所有点,通过递增旋转的x值对它们进行排序,然后对权重执行二元搜索。最理想的是将它在原点和另外两点之间分开。
现在有四点:
1 2
2 1
我们将在此处调查以下几行:
\ 1\|/2 /
\ /|\ /
----+----
/ \|/ \
/ 2/|\1 \
这将返回水平线或垂直线。
有一种可能性 - 正如@Nemo指出的那样,所有这些点都在同一条线上。在这种情况下,没有切线是有意义的。在这种情况下,也可以使用垂直切线。
for v in V
for w in V\{v}
calculate tangent
for tangent and perpendicular tangent
rotate all points such that the tangent is rotated to the y-axis
look for a rotated line in the y-direction that splits the cities optimal
return the best split found
此外,作为几乎所有几何方法,该方法可能遭受多个点位于同一线上的事实,在这种情况下,通过添加简单的旋转,可以包括/排除其中一个点。这确实是一个肮脏的黑客问题。
这个Haskell程序为给定的点列表计算“最佳方向”(如果上述解决方案是正确的):
import Data.List
type Point = (Int,Int)
type WPoint = (Point,Int)
type Direction = Point
dirmul :: Direction -> WPoint -> Int
dirmul (dx,dy) ((xa,ya),_) = xa*dx+ya*dy
dirCompare :: Direction -> WPoint -> WPoint -> Ordering
dirCompare d pa pb = compare (dirmul d pa) (dirmul d pb)
optimalSplit :: [WPoint] -> Direction
optimalSplit pts = (-dy,dx)
where wsum = sum $ map snd pts
(dx,dy) = argmin (bestSplit pts wsum) $ concat [splits pa pb | pa <- pts, pb <- pts, pa /= pb]
splits :: WPoint -> WPoint -> [Direction]
splits ((xa,ya),_) ((xb,yb),_) = [(xb-xa,yb-ya),(ya-yb,xb-xa)]
bestSplit :: [WPoint] -> Int -> Direction -> Int
bestSplit pts wsum d = bestSplitScan cmp ordl 0 wsum
where cmp = dirCompare d
ordl = sortBy cmp pts
bestSplitScan :: ((a,Int) -> (a,Int) -> Ordering) -> [(a,Int)] -> Int -> Int -> Int
bestSplitScan _ [] l r = abs $ l-r
bestSplitScan cmp ((x1,w1):xs) l r = min (abs $ l-r) (bestSplitScan cmp (dropWhile eqf xs) (l+d) (r-d))
where eqf = (==) EQ . cmp (x1,w1)
d = w1+(sum $ map snd $ takeWhile eqf xs)
argmin :: (Ord b) => (a -> b) -> [a] -> a
argmin _ [x] = x
argmin f (x:xs) | (f x) <= f ax = x
| otherwise = ax
where ax = argmin f xs
例如:
*Main> optimalSplit [((0,0),2),((0,1),1),((1,0),1)]
(-1,1)
*Main> optimalSplit [((0,0),2),((0,1),1),((1,0),1),((1,1),2)]
(-1,0)
因此,方向是一条线,如果线将一个元素向左移动,它也会将一个元素移动到顶部。这是第一个例子。对于第二种情况,它选择一条在x方向上移动的线,使其水平分割。该算法仅允许积分,并且在点被放置在同一条线上时不考虑略微调整线:这些全部在或全部用于用于线并行。
答案 3 :(得分:2)
[编辑:大胆的文字与以前在评论中表达的担忧相关。]
[编辑2:正如我之前应该指出的那样,这个答案是tobi303早期答案的补充,它提供了类似的算法。主要目的是表明该算法的基本思想是合理的,并且足够通用。 尽管两个答案中提出的算法细节存在细微差别,但我认为仔细阅读“为什么会起作用”部分,应用于任一算法将表明该算法实际上是完整的]
如果所有城市都在一条直线上 (包括那里的情况 只有一两个城市),那么解决方案很简单。 我假设你可以检测并解决这种情况,所以剩下的了 答案将处理所有其他案件。
如果有两个以上的城市并且它们并非全部共线, “蛮力”解决方案是:
for each city X,
for each city Y where Y is not X
construct a directed line that passes through X and then Y.
Divide the cities in two subsets:
S1 = all the cities to the left of this line
S2 = all the other cities (including cities exactly on the line)
Evaluate the "unfairness" of this division.
以这种方式找到的所有城市的细分中, 选择最不公平的人。归还差异。完成。
请注意,以这种方式找到的行不是“公平地”划分城市的行;它只是与某些这样的线平行。 如果我们必须找到实际的分界线,我们将不得不做更多的工作来弄清楚 准确放置该平行线的位置。但要求的返回值 仅仅是| a-b |。
为什么会这样:
假设L1线以最公平的方式划分城市。 没有唯一行可以做到这一点; (数学上讲)会有无限多行 实现相同的“最佳”划分,但这样的线存在,和 我们需要假设的是L1就是其中一条线。
让城市A在线路的一侧最靠近L1 而城市B最靠近另一侧的L1。 (如果A和B没有唯一标识,那就是有两个或更多 在L1的一侧与“最接近L1”并列的城市, 我们可以设置L2 = L1并跳到下面的L2程序。)
考虑L1在每个方向上的旋转,使用L1穿过的点 AB线作为枢轴点。在至少一个旋转方向上, L1的旋转图像将“击中”其他城市之一, 称之为C,不接触A或B. (这是因为城市不是一条直线。) 此时,C比A或B更接近L1的图像(无论哪个 这些城市与原来的L1位于C的同一侧。 微积分的中值定理告诉我们在某些时候 旋转,C完全接近L1的旋转图像 作为城市A或B,无论哪一个都在该线的同一侧。
这表明总有一条线L2划分城市 尽可能公平地说,有两个城市,D和E, 在L2的同一侧,与“最接近城市的L2”并列 L2那边的城市。
现在考虑通过D和E的两条直线:L3,它们通过 D然后是E和L4,它们通过E然后是D. 在L2的另一侧而不是D和E的城市包括 L3左侧的所有城市,或L4左侧的所有城市。 (请注意,即使发生L3和L4,这也有效 通过两个以上的城市。)
之前描述的程序只是一种找到所有可能的方法 任何执行此行的行可以是行L3或行L4 从解决问题的线L1开始的过程。 (注意虽然L1总是有无限可能的选择, 每个这样的L1导致从有限集合中选择的行L3和L4 穿过两个或更多城市的线路。) 所以程序会找到L1所描述的城市划分, 这是解决问题的方法。