17

对于算法竞赛训练(不是作业),我们从过去一年中得到了这个问题。将其发布到此站点是因为其他站点需要登录。

这就是问题所在:http: //pastehtml.com/view/c5nhqhdcw.html

图片没用所以贴在这里:

它必须在不到一秒的时间内运行,我只能考虑最慢的方法,这是我尝试过的:

with open('islandin.txt') as fin:
    num_houses, length = map(int, fin.readline().split())
    tot_length = length * 4 # side length of square
    houses = [map(int, line.split()) for line in fin] # inhabited houses read into list from text file

def cost(house_no):
    money = 0
    for h, p in houses:
        if h == house_no: # Skip this house since you don't count the one you build on
            continue
        d = abs(h - house_no)
        shortest_dist = min(d, tot_length - d)    
        money += shortest_dist * p
    return money


def paths():
    for house_no in xrange(1, length * 4 + 1):
        yield house_no, cost(house_no)
        print house_no, cost(house_no) # for testing

print max(paths(), key=lambda (h, m): m) # Gets max path based on the money it makes

我目前正在做的是遍历每个位置,然后遍历该位置的每个有人居住的房屋,以找到最大收入的位置。

伪代码:

max_money = 0
max_location = 0
for every location in 1 to length * 4 + 1
    money = 0
    for house in inhabited_houses:
        money = money + shortest_dist * num_people_in_this_house
    if money > max_money
        max_money = money
        max_location = location

这太慢了,因为它是 O(LN) 并且对于最大的测试用例不会在一秒钟内运行。有人可以简单地告诉我如何在最短的运行时间内做到这一点(除非你愿意,否则不需要代码),因为这已经困扰了我很久了。

编辑:必须有一种方法可以在少于 O(L) 的时间内做到这一点,对吧?

4

4 回答 4

10

假设列表houses(x,pop)具有0 <= x < 4*L位置和pop人口的对组成。

我们想要最大化的目标函数是

def revenue(i):
    return sum(pop * min((i-j)%(4*L), 4*L - (i-j)%(4*L)) for j,pop in houses)

朴素算法 O( LN ) 算法很简单:

max_revenue = max(revenue(i) for i in range(4*L))

revenue但是对每个位置进行完全重新评估是非常浪费的。

为避免这种情况,请注意这是一个分段线性函数;所以它的导数是分段常数,在两种点处不连续:

  • 在内部i,导数从slope变为slope + 2*population[i]
  • i在岛上房子对面的点,导数从slope变为slope - 2*population[i]

这使事情变得非常简单:

  1. 我们只需要检查实际的房屋或对面的房屋,因此复杂度下降到 O( N ²)。
  2. 我们知道如何逐家更新,slope而且只需要 O(1) 时间。i-1i
  3. 由于我们知道位置 0 的收入和斜率,并且由于我们知道如何slope迭代更新,因此复杂度实际上下降到 O( N ):在两个连续的房屋/对面的房屋之间,我们可以将斜率乘以获得收入差异的距离。

所以完整的算法是:

def algorithm(houses, L):
    def revenue(i):
        return sum(pop * min((i-j)%(4*L), 4*L - (i-j)%(4*L)) for j,pop in houses)

    slope_changes = sorted(
            [(x, 2*pop) for x,pop in houses] +
            [((x+2*L)%(4*L), -2*pop) for x,pop in houses])

    current_x = 0
    current_revenue = revenue(0)
    current_slope = current_revenue - revenue(4*L-1)
    best_revenue = current_revenue

    for x, slope_delta in slope_changes:
        current_revenue += (x-current_x) * current_slope
        current_slope += slope_delta
        current_x = x
        best_revenue = max(best_revenue, current_revenue)

    return best_revenue

为了简单起见,我曾经sorted()合并两种类型的斜率变化,但这并不是最优的,因为它具有 O( N log N ) 复杂度。如果你想要更好的效率,你可以在 O( N ) 时间内生成一个对应于对面房屋的排序列表,并将其与 O( N ) 中的房屋列表合并(例如与标准库的heapq.merge)。如果你想最小化内存使用,你也可以从迭代器而不是列表中流式传输。

TLDR:该解决方案实现了 O( N ) 的最低可行复杂度。

于 2012-07-25T10:21:04.593 回答
9

这是一个在数学上不太倾斜的解决方案,适用于O(n).

让我们将房屋(索引从 0 开始)划分为两个不相交的集合:

  • F,“前”,人们从 CCW 走到房子的地方
  • B,“回”,人们沿着 CW 走到房子的地方

和一个单独的房子p,标志着工厂将建造的当前位置。

我的插图基于图像中给出的示例。

按照惯例,让我们将一半的房子分配给F,而正好少分配一个给B

  • F包含 6 间房屋
  • B包含 5 间房屋

借助简单的模运算,我们可以轻松访问房屋,(p + offset) % 12这要归功于 Python 对模运算符的理智实现,这与其他一些 流行的语言完全不同。

如果我们任意选择一个位置p,我们可以很容易地确定耗水量O(L)

我们可以再次执行此操作,p以获得O(L^2).

但是,如果我们只移动p一个位置,我们可以O(1)通过稍微巧妙的观察来确定新的消费:当我们设置 时,居住在F(或B分别)的人数决定了消费的F变化量p' = p+1。(以及一些更正,因为F它本身会改变)。我已经尽我所能在这里描绘了这一点。

算法描述

我们最终的总运行时间为O(L).

该算法的程序在帖子的末尾。

但我们可以做得更好。只要集合之间没有房屋变化,相加的cs 和ws 将为零。我们可以计算出这些步骤有多少,并一步完成。

房屋在以下情况下换组: - 何时p在房屋上 - 何时在房屋p对面

在下图中,我已经可视化了算法现在为更新Cs 和Ws 所做的停止。突出显示的是导致算法停止的房子。

优化算法

该算法从一所房子开始(或与之相反,我们稍后会看到原因),在这种情况下恰好是一所房子。

同样,我们既有消费C(B) = 3*1又有C(F) = 2 * 1. 如果我们p向右移动 1,我们将添加4C(B)减去1C(F)如果我们p再次移动,就会发生完全相同的事情。

只要相同的两组房屋分别靠近和远离,Cs的变化是恒定的。

我们现在稍微改变一下定义B:它现在也包含p! (这不会改变上述关于算法优化版本的段落)。

这样做是因为当我们移动到下一步时,我们将增加重复移动的房屋的重量。当前位置的房子p向右移动时正在移动,因此W(B)是正确的加法。

另一种情况是当房子停止移动并再次靠近时。在这种情况下,Cs 会发生巨大变化,因为6*weight从一个C到另一个。那是我们需要停止的另一种情况。

新的计算

我希望很清楚它是如何以及为什么起作用的,所以我将把工作算法留在这里。请询问是否有不清楚的地方。

上):

import itertools

def hippo_island(houses, L):
    return PlantBuilder(houses, L).solution

class PlantBuilder:
    def __init__(self, houses, L):
        self.L = L
        self.houses = sorted(houses)
        self.changes = sorted(
            [((pos + L /2) % L, -transfer) for pos, transfer in self.houses] + 
            self.houses)
        self.starting_position = min(self.changes)[0]

        def is_front(pos_population):
            pos = pos_population[0]
            pos += L if pos < self.starting_position else 0
            return self.starting_position < pos <= self.starting_position + L // 2

        front_houses = filter(is_front, self.houses)
        back_houses = list(itertools.ifilterfalse(is_front, self.houses))

        self.front_count = len(houses) // 2
        self.back_count = len(houses) - self.front_count - 1
        (self.back_weight, self.back_consumption) = self._initialize_back(back_houses)
        (self.front_weight, self.front_consumption) = self._initialize_front(front_houses)
        self.solution = (0, self.back_weight + self.front_weight)
        self.run()

    def distance(self, i, j):
        return min((i - j) % self.L, self.L - (i - j) % self.L)

    def run(self):
        for (position, weight) in self.consumptions():
            self.update_solution(position, weight)

    def consumptions(self):
        last_position = self.starting_position
        for position, transfer in self.changes[1:]:
            distance = position - last_position
            self.front_consumption -= distance * self.front_weight
            self.front_consumption += distance * self.back_weight

            self.back_weight += transfer
            self.front_weight -= transfer

            # We are opposite of a house, it will change from B to F
            if transfer < 0:
                self.front_consumption -= self.L/2 * transfer
                self.front_consumption += self.L/2 * transfer


            last_position = position
            yield (position, self.back_consumption + self.front_consumption)

    def update_solution(self, position, weight):
        (best_position, best_weight) = self.solution
        if weight > best_weight:
            self.solution = (position, weight)

    def _initialize_front(self, front_houses):
        weight = 0
        consumption = 0
        for position, population in front_houses:
            distance = self.distance(self.starting_position, position)
            consumption += distance * population
            weight += population
        return (weight, consumption)

    def _initialize_back(self, back_houses):
        weight = back_houses[0][1]
        consumption = 0
        for position, population in back_houses[1:]:
            distance = self.distance(self.starting_position, position)
            consumption += distance * population
            weight += population
        return (weight, consumption)

O(L)

def hippo_island(houses):
    return PlantBuilder(houses).solution

class PlantBuilder:
    def __init__(self, houses):
        self.houses = houses
        self.front_count = len(houses) // 2
        self.back_count = len(houses) - self.front_count - 1
        (self.back_weight, self.back_consumption) = self.initialize_back()
        (self.front_weight, self.front_consumption) = self.initialize_front()
        self.solution = (0, self.back_weight + self.front_weight)
        self.run()

    def run(self):
        for (position, weight) in self.consumptions():
            self.update_solution(position, weight)

    def consumptions(self):
        for position in range(1, len(self.houses)):
            self.remove_current_position_from_front(position)

            self.add_house_furthest_from_back_to_front(position)
            self.remove_furthest_house_from_back(position)

            self.add_house_at_last_position_to_back(position)
            yield (position, self.back_consumption + self.front_consumption)

    def add_house_at_last_position_to_back(self, position):
        self.back_weight += self.houses[position - 1]
        self.back_consumption += self.back_weight

    def remove_furthest_house_from_back(self, position):
        house_position = position - self.back_count - 1
        distance = self.back_count
        self.back_weight -= self.houses[house_position]
        self.back_consumption -= distance * self.houses[house_position]

    def add_house_furthest_from_back_to_front(self, position):
        house_position = position - self.back_count - 1
        distance = self.front_count
        self.front_weight += self.houses[house_position]
        self.front_consumption += distance * self.houses[house_position]

    def remove_current_position_from_front(self, position):
        self.front_consumption -= self.front_weight
        self.front_weight -= self.houses[position]

    def update_solution(self, position, weight):
        (best_position, best_weight) = self.solution
        if weight > best_weight:
            self.solution = (position, weight)

    def initialize_front(self):
        weight = 0
        consumption = 0
        for distance in range(1, self.front_count + 1):
            consumption += distance * self.houses[distance]
            weight += self.houses[distance]
        return (weight, consumption)

    def initialize_back(self):
        weight = 0
        consumption = 0
        for distance in range(1, self.back_count + 1):
            consumption += distance * self.houses[-distance]
            weight += self.houses[-distance]
        return (weight, consumption)

结果:

>>> hippo_island([0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 1, 2])
(7, 33)
于 2012-07-25T16:21:26.987 回答
0

我将提供一些提示,以便您仍然可以为自己带来一些挑战。


让我从一个高度简化的版本开始:

一条笔直的街道上有 N 栋房子,要么是有人的,要么是空的。

0 1 1 0 1

让我们计算他们的分数,知道第 n 个房子的分数等于到其他非空房子的所有距离的总和。所以第一个房子的得分是1+2+4 = 7,因为还有 3 个其他人住的房子,它们的距离是 1、2、4。

完整的分数数组如下所示:

7 4 3 4 5

那怎么计算呢?显而易见的方法是...

for every house i
    score(i) = 0
    for every other house j
        if j is populated, score(i) += distance(i, j)

这给了你 O(N^2) 的复杂性。但是,有一种更快的方法可以计算 O(N) 中的所有分数,因为它没有嵌套循环。它与前缀和有关。你能找到吗?

于 2012-07-22T15:33:15.727 回答
0

不用计算每一间房子!!!

它还没有完全开发,但我认为值得思考:

N

N为所有房屋的数量,n应为部分房屋的“地址”(数量)。

如果你在岛上走一圈,你会发现每经过一个房子,n 就增加 1。如果您到达nN的房子,则下一个房子的数字为 1。

让我们使用不同的编号系统:将每个门牌号加 1。然后n从 0 变为N -1。这与数字模N的行为方式相同。

升是门牌号n的函数(模N

您可以通过构建所有距离乘积和居住在那里的人的总和来计算每个门牌的升数。

您还可以绘制该函数的图表: x 是n, y 是升数。

函数是周期性的

如果您了解模数的含义,您就会明白您刚刚绘制的图形只是周期函数的一个周期,因为 Litre( n ) 等于 Litre( n + x * N ) 其中 x 是一个整数(可能也是负面的)。

如果N很大,则函数为“伪连续”

我的意思是:如果N真的很大,那么如果你从房子a搬到它的邻居房子a+1 ,升的数量不会有太大变化。所以你可以使用插值的方法。

您正在寻找周期性伪连续函数的“全局”最大值的位置(仅在一个周期内真正全局)

这是我的建议:

步骤 1:选择一个大于 1 且小于N的距离d。我不能说为什么,但我会使用 d=int(sqrt( N )) (也许有更好的选择,试试看)。 第 2 步:计算房屋 0、d、2d、3d、... 的升数 第 3 步:您会发现一些值高于他们的邻居。使用这个高点和他们的邻居使用插值方法来计算更多接近高点的点(间隔分割)。

只要您有时间,就对其他高点重复此插值(您有 1 秒,这是很长的时间!)

如果你看到,从一个高点跳到另一个高点,全局最大值一定在别处。

于 2012-07-25T13:11:21.537 回答