算法(概率求解)实现最快的运行时间

时间:2012-07-22 13:22:33

标签: python algorithm

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

这是问题所在: 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 个答案:

答案 0 :(得分: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是非常浪费的。

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

  • at house i,衍生工具从slope更改为slope + 2*population[i]
  • 位于岛上房屋i对面的点,衍生工具从slope更改为slope - 2*population[i]

这使事情变得非常简单:

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

    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 < / em>)(例如,使用标准库的heapq.merge)。如果要最小化内存使用,也可以从迭代器而不是列表流式传输。

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

答案 1 :(得分:9)

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

让我们将房屋(索引从0开始)分成两个分离集:

  • F,“前线”,人们沿着CCW走到房子里
  • B,“返回”,人们走到房子里

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

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

按照惯例,我们可以将一半的房屋分配给F,而将B分别减少一半。

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

使用简单的模块化算法,我们可以通过(p + offset) % 12轻松访问房屋,这要归功于Python模数运算符的理智实现,与some other popular {{3完全不同}}

如果我们任意选择p的位置,我们可以在O(L)中轻松确定水的消耗量。

我们可以再次对p的不同位置执行此操作,以达到O(L^2)的运行时间。

但是,如果我们只将p移到一个位置,如果我们做出一个有点聪明的观察,我们可以确定O(1)中的新消费量:生活在F的人数(或B分别确定F设置p' = p+1时消费量F的变化情况。 (以及一些更正,因为O(L)本身会改变)。我试图尽我最大的能力来描绘这一点。

algorithm depiction

我们最终的总运行时间为c

此算法的程序位于帖子的末尾。

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

在以下情况下更改套房:   - 当p在房子上时   - 当C与房子相对时

在下图中,我已经可视化了算法现在用于更新WC(B) = 3*1的停止。 突出显示的是房子,导致算法停止。

optimized algorithm

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

同样,我们同时拥有消费C(F) = 2 * 1p。如果我们将4向右移动,我们会将C(B)添加到1并从C(F)中减去p。如果我们再次转移C,就会发生同样的事情。

只要相同的两组房屋分别越来越远,B s的变化是不变的。

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

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

另一种情况是房子停止移动并再次靠近。在这种情况下,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) 转到另一个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) 。那是我们需要停止的另一种情况。

new calculations

我希望很清楚这是如何以及为什么会这样,所以我将把工作算法留在这里。如果不清楚,请询问。

O(N):

>>> hippo_island([0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 1, 2])
(7, 33)

O(L)

{{1}}

结果:

{{1}}

答案 2 :(得分: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)中的所有分数,因为它没有嵌套循环。它与前缀总和有关。你能找到吗?

答案 3 :(得分:0)

没有必要计算每个房子!!!

它尚未完全开发,但我认为值得考虑:

模数 N

N 是所有房屋的数量, n 应该是某些房屋的“地址”(数字)。

如果你在岛上走动,你会发现你通过的每个房子的n都会提高1。如果您到达 n N 的房子,那么下一所房子的编号为1。

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

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

您可以通过构建距离和居住在那里的所有产品的总和来计算每个房屋的升数量。

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

该功能是定期的

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

如果 N 很大,则功能为“伪连续”

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

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

这是我的建议:

步骤1:选择距离 d 大于1且小于 N 的距离。我不能说为什么,但我会使用d = int(sqrt( N ))(也许可能有更好的选择,尝试一下)。
第2步:计算House 0,d,2d,3d,......的升数 第3步:您会发现一些高于其两个邻居的值。使用此高点,他们的邻居使用插值方法为它们提供计算更接近高点的点(区间分割)。

只要你有时间(你有1秒,这是很长的时间!)重复这个插值为其他高点!

如果您看到全局最大值必须在其他地方,则从一个高点跳转到另一个高点。