17

アルゴリズムの競争トレーニング(宿題ではない)については、昨年からこの質問がありました。他のサイトはログインが必要だったので、このサイトに投稿しました。

これが問題です:http: //pastehtml.com/view/c5nhqhdcw.html

画像が機能しなかったので、ここに投稿してください:

それは1秒未満で実行する必要があり、私はそれを行うための最も遅い方法についてしか考えることができません、これは私が試したものです:

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)であり、最大のテストケースでは1秒未満で実行されないため、遅すぎます。これは何年もの間私を悩ませてきたので、誰かが最短の実行時間でそれを行う方法を簡単に教えてもらえますか(あなたが望む場合を除いてコードは必要ありません)。

編集:O(L)未満でこれを行う方法があるはずですよね?

4

4 に答える 4

10

リストが場所と人口housesのペア(x,pop)で構成されているとします。0 <= x < 4*Lpop

最大化したい目的関数は

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しかし、場所ごとに完全に再評価することは非常に無駄です。

これを回避するには、これが区分的線形関数であることに注意してください。したがって、その導関数は区分的に一定であり、2種類の点で不連続性があります。

  • iでは、導関数はからslopeに変わりますslope + 2*population[i]
  • i島の家の反対側にあるポイントで、導関数はからslopeに変わりますslope - 2*population[i]

これにより、物事が非常に簡単になります。

  1. 実際の家または反対側の家を調べるだけでよいので、複雑さはO(N²)に下がります
  2. slope私たちは家から家i-1へと更新する方法を知っていますi、そしてそれはO(1)時間だけを必要とします。
  3. 場所0での収益と勾配がわかっており、繰り返し更新する方法がわかっているため、複雑さは実際にはO( Nslope )に下がります。2つの連続する家/家の反対側の間では、勾配に次の値を掛けることができます。収益の差を取得するための距離。

したがって、完全なアルゴリズムは次のとおりです。

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()に、2種類の勾配の変更をマージするために使用しましたが、O(N log N)の複雑さがあるため、これは最適ではありません。より良い効率が必要な場合は、O(N)時間で反対側の家に対応する並べ替えられたリストを生成し、それをO(N)の家のリストとマージできます(たとえば、標準ライブラリのheapq.merge)。メモリ使用量を最小限に抑えたい場合は、リストの代わりにイテレータからストリーミングすることもできます。

TLDR:このソリューションは、O( N )の実行可能な最小の複雑さを実現します。

于 2012-07-25T10:21:04.593 に答える
9

これは、で機能する数学的な傾向の少ないソリューションですO(n)

家(インデックス付けは0から始まります)を2つの互いに素なセットに分割しましょう:

  • F、「フロント」、人々がCCWを家まで歩く
  • B、「戻る」、人々がCWを家まで歩く

pそして、プラントが建設される現在の位置を示す一軒家。

私は、画像に示されている例に基づいてイラストを作成しました。

慣例により、家の半分をに割り当てF、正確に1つ少ない家をに割り当てましょうB

  • F6軒の家が含まれています
  • B5軒の家が含まれています

単純なモジュラー演算を使用すると、他の一般的な言語(p + offset) % 12とはまったく異なり、Pythonのモジュロ演算子の適切な実装のおかげで家に簡単にアクセスできます。

の位置を任意に選択すればp、水の消費量を簡単に判断できO(L)ます。

pのランタイムに到達するための別の位置に対して、これをもう一度やり直すことができますO(L^2)

ただし、1つの位置だけシフトする場合、いくらか巧妙な観察を行うとp、新しい消費量を決定できます。住んでいる人の数(またはそれぞれ)によって、設定したときに消費量がどれだけ変化するかが決まります。(そしてそれ自体が変わるのでいくつかの修正)。私はこれを私の能力の限りを尽くしてここに描写しようとしました。O(1)FBFp' = p+1F

アルゴリズムの描写

最終的に、合計実行時間はになりO(L)ます。

このアルゴリズムのプログラムは、投稿の最後にあります。

しかし、私たちはもっとうまくやることができます。セット間で家が変わらない限り、追加されるcsとsはゼロになります。wこれらのステップがいくつあるかを計算し、1つのステップで実行できます。

家は次の場合にセットを変更します:-家にいるとき-家の反対側にあるpときp

C次の図では、アルゴリズムがsとsを更新するための停止を視覚化していますW。強調表示されているのは、アルゴリズムを停止させる家です。

最適化されたアルゴリズム

アルゴリズムは家から始まります(またはその反対です。理由は後で説明します)。この場合、それはたまたま家です。

C(B) = 3*1繰り返しますが、消費との両方がありC(F) = 2 * 1ます。p右に1つシフト4すると、に加算C(B)および減算1C(F)ます。pもう一度シフトすると、まったく同じことが起こります。

同じ2組の家がそれぞれ近づいたり遠ざかったりする限り、Csの変化は一定です。

ここで、の定義をB少し変更します。これには、p!も含まれるようになります。(これは、アルゴリズムのこの最適化されたバージョンに関する上記の段落を変更しません)。

これは、次のステップに進むときに、繰り返し移動する家の重量を追加するためです。現在の位置にある家はp、右に移動すると離れていくのでW(B)、正しい加数です。

もう1つのケースは、家が離れなくなり、再び近づく場合です。その場合、 sは一方から他方に移動するCため、大幅に変化します。それは私たちが停止する必要があるもう一つのケースです。6*weightC

新しい計算

これがどのように、そしてなぜ機能するのかが明確になっていることを願っています。そこで、機能するアルゴリズムはここに残しておきます。不明な点がないかお尋ねください。

の上):

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

Nはすべての家の数であり、nはいくつかの家の「住所」(数)です。

島を歩き回ると、通り過ぎる家ごとにnが1ずつ上がっていることがわかります。nNの家にたどり着くと、次の家の番号は1になります。

別の番号付けシステムを使用してみましょう。すべての家番号を1ずつ増やします。次にnは0からN -1になります。これは、 Nを法とする数値の動作と同じです。

リットルは、家番号n(モジュロN)の関数です。

距離とそこに住む人々のすべての製品の合計を作成することにより、各家のリットル数を計算できます。

その関数のグラフを描くこともできます。xはn、yはリットル数です。

関数は定期的です

モジュロの意味を理解すると、Litre(n)はLitre(n + x * N)と等しくないため、描画したグラフは周期関数の1つの周期にすぎないことがわかります。ここで、xは整数です(ネガティブにもなります)。

Nが大きい場合、関数は「疑似連続」です。

つまり、 Nが本当に大きい場合、家aからその隣の家a + 1に移動しても、リットルの量はあまり変化しません。したがって、補間の方法を使用できます。

周期的な疑似連続関数の「グローバル」最大値の場所を探しています(1つの周期内で実際にグローバルのみ)

これは私の提案です:

ステップ1: 1より大きくNより小さい距離dを選択します。理由はわかりませんが、d = int(sqrt(N))を使用します(より良い選択肢があるかもしれません。試してみてください)。 ステップ2:ハウス0、d、2d、3d、...のリットルを計算します。ステップ3:両方の隣人よりも高い値がいくつか見つかります。この高点を使用し、それらの隣接点は、内挿法を使用してそれらにフィードし、高点に近いより多くの点を計算します(間隔分割)。

時間がある限り、他の高いポイントに対してこの補間を繰り返します(1秒あります。これは長い時間です!)

グローバル最大値は他の場所にある必要があることがわかった場合は、あるハイポイントから別のハイポイントにジャンプします。

于 2012-07-25T13:11:21.537 に答える
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 に答える