王国的公平分裂

时间:2015-05-26 20:33:17

标签: algorithm

最近,我参加了编程比赛。有一个问题,我仍然在考虑。编程语言并不重要,但我已经用C ++编写了它。任务是:

  

如您所知,Flatland位于飞机上。有n   Flatland的城市,这些城市中的第i个位于(xi,   YI)。居住在第i个城市的 ai 公民。国王   Flatland决定将王国分配给他的两个儿子。他   想要以无限直线的形式建造一堵墙;每个   部分将由其中一个儿子统治。墙不能通过   通过任何城市。为了避免兄弟之间的嫉妒,人口   两部分必须尽可能接近;正式地,如果 a b 是   居住在城市中的第一个和第二个城市的公民总数   第二部分, | a - b | 的值必须最小化。   帮助国王找到最佳分工。城市数量较少   超过1000.所有坐标都是整数。算法输出   应该是最小的 | a-b |

的整数

好的,如果我知道线的方向,那将是非常简单的任务 - 二分法搜索:Black numbers are population of cities, and red is total population in that part

我不想要代码,我想要点子,因为我没有。如果我抓住了想法,我可以编写代码!

我不知道最佳方向,但我认为可以通过某种方式找到它。那可以找到它还是以其他方式解决这个问题?

水平/垂直线不是最佳的示例:

1

\
 \
2 \      1

4 个答案:

答案 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点开始,它们在飞机上的位置确实很重要。)

enter image description here

共线点

经过一些讨论和富有成效的评论,我得出的结论是,共线点不是真正的问题。在评估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)

n 小于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所描述的城市划分, 这是解决问题的方法。