Python 实现最快运行时间的算法(问题求解)

Python 实现最快运行时间的算法(问题求解),python,algorithm,Python,Algorithm,在一次算法竞赛培训(不是家庭作业)中,我们在过去一年中收到了这个问题。已将其发布到此网站,因为其他网站需要登录 这就是问题所在: 图像不起作用,因此将其发布在此处: 它必须在不到一秒钟的时间内运行,我只能想到最慢的方法,这就是我尝试的: with open('islandin.txt') as fin: num_houses, length = map(int, fin.readline().split()) tot_length = length * 4 # side len

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

这就是问题所在:

图像不起作用,因此将其发布在此处:

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

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)的时间内,一定有办法做到这一点,对吗?

我将提供一些提示,这样你仍然可以为自己带来一些挑战


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

在一条笔直的街道上有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)中的所有分数,因为它没有嵌套循环。它与前缀和有关。你能找到它吗?

假设列表
房屋
由成对的
(x,pop)
0组成,没有必要计算每个房屋

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

模N

N是所有房屋的数量,N是一些房屋的“地址”(数量)

如果你在岛上走走,你会发现每经过一所房子,n就会增加1。如果你到达n为n的房子,那么下一个房子的数字是1

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

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

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

你也可以画一个函数图:x是n,y是升数

该函数是周期性的

如果你理解了模的意思,你就会明白你刚才画的图只是周期函数的一个周期,因为升(n)等于升(n+x*n),其中x是一个整数(也可能是负数)

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

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

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

我的建议是:

步骤1:选择一个大于1小于N的距离d。我不知道为什么,但我会使用d=int(sqrt(N))(也许有更好的选择,试试看)。
步骤2:计算房屋0、d、2d、3d……的升数。。。 第三步:你会发现一些价值观高于他们的邻居。使用该高点及其相邻点,使用插值方法计算接近该高点的更多点(区间分割)

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


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

这里有一个不太倾向于数学的解决方案,适用于
O(n)

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

  • F
    ,“前面”,人们逆时针走向房子的地方
  • B
    ,“back”,人们朝房子走去的地方
还有一个单独的房子,
p
,标志着工厂将要建造的当前位置

我已根据图中给出的示例进行了说明

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

  • F
    包含6个房屋
  • B
    包含5个房屋
通过简单的模运算,我们可以通过
(p+offset)%12
轻松地访问房屋,这要归功于Python对模运算符的合理实现,这与Python完全不同

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

对于
p
的不同位置,我们可以重新进行此操作,以达到
O(L^2)
的运行时

然而,如果我们只将
p
移动一个位置,我们可以确定
O(1)
中的新消费量,如果我们做一个稍微聪明的观察:生活在
F
中的人数(或
B
分别)决定当我们设置
p'=p+1
F
的消费量的变化量。(还有一些修正,因为
F
本身会改变)。我已经尽力在这里描述了这一点

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

这个算法的程序在文章的末尾

但我们可以做得更好。只要没有时间
def revenue(i):
    return sum(pop * min((i-j)%(4*L), 4*L - (i-j)%(4*L)) for j,pop in houses)
max_revenue = max(revenue(i) for i in range(4*L))
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
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)
>>> hippo_island([0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 1, 2])
(7, 33)