Побудуйте невеликий і збалансований мобільний


18

Вам дана купа ваг, і ваше завдання - створити невеликий збалансований мобільний за допомогою цих ваг.

Вихідні дані - це перелік цілих ваг у діапазоні від 1 до 9 включно. Можуть бути дублікати.

Вихід - це зображення ascii мобільного, який, коли його повісять, буде врівноважувати. Мабуть, найкраще показано на прикладі:

вхід

3 8 9 7 5

можливий вихід

         |
   +-----+---------+
   |               |
+--+-+        +----+------+
|    |        |           |
8   ++--+     7           5
    |   |
    9   3

Ви повинні використовувати символи ascii, як показано. Горизонтальний і вертикальний сегменти можуть бути будь-якої довжини. Жодна частина мобільного телефону не може торкатися (горизонтально або вертикально) іншої непоєднаної частини мобільного телефону. Всі ваги повинні бути вивішені з вертикального відрізка довжиною не менше 1, і повинен бути вертикальний відрізок, з якого висить весь мобільний.

Розмір мобільного телефону є загальною кількістю +, -і |символами , необхідним для створення його. Нижчі розміри краще.

Ви можете розмістити стільки з’єднань на сегменті, скільки хочете. Наприклад:

вхід

2 3 3 5 3 9

можливий вихід

           |
   +---+---+-----------+
   |   |               |
+--+-+ 5               9
|  | |
2  | 3
   |
  +++
  | |
  3 3

Виграюча програма - це та, яка може генерувати найменший середній розмір мобільних пристроїв для тестового набору входів. Справжній тест є надсекретним для запобігання жорсткого кодування, але він буде приблизно таким:

8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7
1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 7 7
3 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7

Фізика також займається?
ВИ

1
@ S.Mark: Я думаю, ви могли це сказати. Для фізично ослаблених сума сума total_weight_hung_from_point * distance_of_point_from_pivotповинна бути однаковою з обох сторін точки зрізу.
Кіт Рендалл

Можливо, щоб полегшити вивчення діаграм, зробити так, щоб одна смужка дорівнювала приблизно двом дефісам? На сьогоднішній день ваші діаграми виглядають нерівномірно.
Томас О

Відповіді:


5

Пітон 2.

Я трохи обманюю:

  • Я будую лише мобільні телефони з однією горизонталлю. У мене є відчуття (але я цього не довів), що оптимальний мобільний при даних умовах насправді завжди має лише одну горизонталь. Правка: Не завжди правда; з 2 2 9 1Nabb знайшов зустрічний приклад у коментарях нижче:

    Size 18:                Size 16:
       |                        |
    +-++--+-----+            +--++-+
    | |   |     |            |   | |
    2 9   2     1           -+-  9 1
                            | |
                            2 2
    
  • Я просто роблю дурне насильство:

    1. Дані ваги перемішуються випадковим чином.
    2. Дві ваги одночасно розміщуються на мобільному у кращих положеннях, щоб він залишався врівноваженим.
    3. Якщо отриманий мобільний телефон кращий за будь-який, який ми мали раніше, пам’ятайте про це.
    4. Промийте та повторіть, доки не буде визначено кількість секунд.

Мої результати для ваших зразків даних; кожен пробіг по 5 секунд (я усвідомлюю, що для малих це смішно - просто пройти всі можливі перестановки було б швидше). Зауважте, що оскільки існує випадковий елемент, наступні запуски можуть знайти кращі або гірші результати.

3 8 9 7 5
Tested 107887 mobiles, smallest size 20:
        |
+-+-----+-+--+
| |     | |  |
5 3     7 9  8

2 3 3 5 3 9
Tested 57915 mobiles, smallest size 23:
      |
+--+-++--+-+---+
|  | |   | |   |
3  5 9   3 3   2

8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7
Tested 11992 mobiles, smallest size 50:
                |
+-+-+-+--+-+-+-+++-+-+--+-+-+-+-+
| | | |  | | | | | | |  | | | | |
8 8 8 8  8 8 8 8 8 8 8  7 8 8 8 8

1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 7 7
Tested 11119 mobiles, smallest size 62:
                    |
+-+-+-+-+-+--+-+-+-+++-+-+-+--+-+-+-+-+-+
| | | | | |  | | | | | | | |  | | | | | |
2 7 5 6 6 8  3 2 3 7 9 7 8 1  1 7 9 5 4 4

3 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7
Tested 16301 mobiles, smallest size 51:
                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | |
4 6 5 7 7 4 6 5 3 5 6 4 7 6 7 5 4

Код (багатослівний, оскільки це не код гольфу):

import time, random

def gcd(a, b):
    while b > 0:
        a, b = b, a % b
    return a

class Mobile(object):
    def __init__(self):
        self.contents = [None];
        self.pivot = 0;

    def addWeights(self, w1, w2):
        g = gcd(w1, w2)
        m1 = w2 / g
        m2 = w1 / g
        mul = 0
        p1 = -1
        while True:
            if p1 < 0:
                mul += 1
                p1 = mul * m1
                p2 = -mul * m2
            else:
                p1 *= -1
                p2 *= -1
            if self.free(p1) and self.free(p2):
                self.add(w1, p1)
                self.add(w2, p2)
                return

    def add(self, w, pos):
        listindex = self.pivot - pos 
        if listindex < 0:
            self.contents = [w] + (abs(listindex) - 1) * [None] + self.contents
            self.pivot += abs(listindex)
        elif listindex >= len(self.contents):
            self.contents += (listindex - len(self.contents)) * [None] + [w]
        else:
            self.contents[listindex] = w

    def at(self, pos):
        listindex = self.pivot - pos
        if 0 <= listindex < len(self.contents):
            return self.contents[listindex]
        return None

    def free(self, pos):
        return all(self.at(pos + d) is None for d in (-1, 0, 1))

    def score(self):
        return 1 + 2 * len(self.contents) - self.contents.count(None)

    def draw(self):
        print self.pivot * " " + "|"
        print "".join("+" if c is not None or i == self.pivot else "-" for i, c in enumerate(self.contents))
        print "".join("|" if c is not None else " " for c in self.contents)
        print "".join(str(c) if c is not None else " " for c in self.contents)

    def assertBalance(self):
        assert sum((i - self.pivot) * (c or 0) for i, c in enumerate(self.contents)) == 0


weights = map(int, raw_input().split())

best = None
count = 0

# change the 5 to the number of seconds that are acceptable
until = time.time() + 5

while time.time() < until:
    count += 1
    m = Mobile()

    # create a random permutation of the weights
    perm = list(weights)
    random.shuffle(perm)

    if len(perm) % 2:
        # uneven number of weights -- place one in the middle
        m.add(perm.pop(), 0)

    while perm:
        m.addWeights(perm.pop(), perm.pop())

    m.assertBalance() # just to prove the algorithm is correct :)
    s = m.score()
    if best is None or s < bestScore:
        best = m
        bestScore = s

print "Tested %d mobiles, smallest size %d:" % (count, best.score())
best.draw()

@Nabb: Більш високі ваги, ніж 9, неможливі. Що стосується 1 9 2 8породження 1-------8+-9--2; я не можу придумати нічого кращого (але я б на це не покладався) - у тебе є щось?
balpha

1
@balpha: Nevermind, не думав прямо, коли я коментував це раніше. Я чомусь думав, що ти можеш дотримуватися їх як 1-9 та 2-8, але, очевидно, самі ці пари не врівноважують!
Nabb

Гаразд, ось який, можливо, кращий з декількома шарами: 2 2 9 1тобто (2 + 2) * 3 = 9 + 1 * 3 на 16 розмір, замість 2-9+--2----1якого 18. Я думаю, що є поріг (можливо, 5 або 6 ) після якого один горизонтальний ряд завжди оптимальний.
Nabb

@Nabb: Так; це справді хороший зустрічний приклад.
balpha

@Nabb, Одинарна смужка з 2-2-+9-1залишками, з оцінкою 13 (4*2+2*2 = 9*1+1*3). Тож я не думаю, що один із них є хорошим контрприкладом.
Кіт Рендалл

1

Ну, це старе питання, але я щойно побачив, як він з’являється на вкладці "Найпопулярніші питання", тож ось моє (оптимальне) рішення:

#include <stdio.h>
#include <limits.h>
#include <math.h>
#include <stdlib.h>

int main(int argc, const char *const *argv) {
    if(argc < 2) {
        fprintf(stderr,
            "Balances weights on a hanging mobile\n\n"
            "Usage: %s <weight1> [<weight2> [...]]\n",
            argv[0]
        );
        return 1;
    }
    int total = argc - 1;
    int values[total];
    int maxval = 0;
    for(int n = 0; n < total; ++ n) {
        char *check = NULL;
        long v = strtol(argv[n+1], &check, 10);
        if(v <= 0 || v > INT_MAX || *check != '\0') {
            fprintf(stderr,
                "Weight #%d (%s) is not an integer within (0 %d]\n",
                n + 1, argv[n+1], INT_MAX
            );
            return 1;
        }
        values[n] = (int) v;
        if(values[n] > maxval) {
            maxval = values[n];
        }
    }
    int maxwidth = (int) log10(maxval) + 1;
    for(int n = 0; n < total; ++ n) {
        int width = (int) log10(values[n]) + 1;
        fprintf(stdout,
            "%*s\n%*d\n",
            (maxwidth + 1) / 2, "|",
            (maxwidth + width) / 2, values[n]
        );
    }
    return 0;
}

З погляду на правила я майже впевнений, що це не обман, хоча відчуваю, що це так. Це просто виведе всі задані числа у вертикальному ланцюжку, за загальною вартістю 2 * число_обігу входів (що є мінімальним можливим, оскільки кожне число повинно мати смугу над ним, незалежно від компонування). Ось приклад:

./mobile 3 8 9 7 5

Виробляє:

|
3
|
8
|
9
|
7
|
5

Що, звичайно, в ідеальному балансі.


Я спочатку збирався спробувати щось більше в дусі цього виклику, але швидко виявилося, що все одно просто оптимізував цю структуру


Напевно, не зрозуміло з мого опису, але ви не можете підключити a |до дна ваги.
Кіт Рендалл

@KeithRandall ах добре; маючи на увазі, що, можливо, мені доведеться зайнятися правильним вирішенням цього питання.
Дейв

1

Ось рішення, яке грубо примушує найменше рішення з одного ряду. Код повторює всі перестановки і обчислює центр маси для кожної. Якщо центр маси має цілі координати, ми знайшли рішення.

Після того, як всі перестановки були випробувані, ми додаємо сегмент до суміші (еквівалентний вазі маси 0) у нашому поточному наборі ваг і повторимо спробу.

Щоб запустити програму, зробіть python balance.py 1 2 2 4.

#!/usr/bin/env python3
import itertools, sys

# taken from http://stackoverflow.com/a/30558049/436792
def unique_permutations(elements):
    if len(elements) == 1:
        yield (elements[0],)
    else:
        unique_elements = set(elements)
        for first_element in unique_elements:
            remaining_elements = list(elements)
            remaining_elements.remove(first_element)
            for sub_permutation in unique_permutations(remaining_elements):
                yield (first_element,) + sub_permutation

def print_solution(cm, values):
    print(('  ' * cm) + '|')
    print('-'.join(['-' if v == 0 else '+'  for v in values]))
    print(' '.join([' ' if v == 0 else '|'  for v in values]))
    print(' '.join([' ' if v == 0 else str(v) for v in values]))



input = list(map(int, sys.argv[1:]))
mass = sum(input)
while True:
    n = len(input)
    permutations = filter(lambda p: p[0] != 0 and p[n-1] != 0, unique_permutations(input))
    for p in permutations:
        cm = 0
        for i in range(n):
            cm += p[i] * i;
        if (cm % mass == 0):
            print_solution(cm//mass, p)
            sys.exit(0)
    input.append(0)

яка виробляє такі найкращі рішення:

    |
+-+-+-+-+
| | | | |
8 3 9 5 7


    |
+-+-+-+-+-+
| | | | | |
9 2 3 5 3 3

                |
+-+-+-+-+-+-+---+-+-+-+-+-+-+-+-+
| | | | | | |   | | | | | | | | |
8 8 8 8 8 8 8   8 8 8 8 8 8 8 8 7


                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | | | | |
1 1 2 2 3 3 4 4 8 8 5 5 6 6 7 7 7 7 9 9


                  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | |
3 4 4 4 4 5 5 5 5 6 7 6 7 7 7 6 6

0

Пітон 3

Я вважаю, що це не гірше, ніж на 1, ніж оптимальне в будь-якому з тестових випадків, і це робиться за 5 секунд.

В основному я використовую підхід одного бару. Я випадковим чином впорядковую введення, після чого вставляю ваги на планку по черзі. Кожен елемент або ставиться в положення, яке мінімізує зайву вагу з обох боків, або друге найкраще положення з цієї точки зору, використовуючи перше 75% часу та друге 25% часу. Потім я перевіряю, чи мобільний врівноважений, і чи кращий за найкращий мобільний телефон, знайдений до цього часу. Я зберігаю найкращий, потім зупиняю та роздруковую після 5 секунд пошуку.

Результати, за 5 секунд:

py mobile.py <<< '3 8 7 5 9'
Best mobile found, score 15:
    |    
+-+-+-+-+
| | | | |
8 7 3 5 9
py mobile.py <<< '2 2 1 9'
Best mobile found, score 13:
   |    
+-++-+-+
| |  | |
1 9  2 2
py mobile.py <<< '2 3 3 5 3 9'
Best mobile found, score 18:
      |    
+-+-+-+-+-+
| | | | | |
2 3 3 5 9 3
py mobile.py <<< '8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7'
Best mobile found, score 49:
                |               
+-+--+-+-+-+-+-+++-+-+-+-+-+-+-+
| |  | | | | | | | | | | | | | |
7 8  8 8 8 8 8 8 8 8 8 8 8 8 8 8
\py mobile.py <<< '1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 7 7'
Best mobile found, score 61:
                    |                   
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+
| | | | | | | | | | | | | | | | | | |  |
1 7 7 5 4 3 1 9 6 7 8 2 2 9 3 7 6 5 8  4
py mobile.py <<< '3 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7'
Best mobile found, score 51:
                |                
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | |
4 4 6 7 7 4 5 7 6 6 5 4 6 3 5 5 7

Код:

import random
import time

class Mobile:
    def __init__(self):
        self.contents = {}
        self.lean = 0

    def usable(self, loc):
        return not any(loc + k in self.contents for k in (-1,0,1))
    def choose_point(self, w):
        def goodness(loc):
            return abs(self.lean + w * loc)
        gl = sorted(list(filter(self.usable,range(min(self.contents.keys() or [0]) - 5,max(self.contents.keys() or [0]) + 6))), key=goodness)
        return random.choice((gl[0], gl[0], gl[0], gl[1]))

    def add(self, w, loc):
        self.contents[loc] = w
        self.lean += w*loc

    def __repr__(self):
        width = range(min(self.contents.keys()), max(self.contents.keys()) + 1)
        return '\n'.join((''.join(' ' if loc else '|' for loc in width),
                          ''.join('+' if loc in self.contents or loc == 0 else '-' for loc in width),
                          ''.join('|' if loc in self.contents else ' ' for loc in width),
                          ''.join(str(self.contents.get(loc, ' ')) for loc in width)))

    def score(self):
        return max(self.contents.keys()) - min(self.contents.keys()) + len(self.contents) + 2

    def my_score(self):
        return max(self.contents.keys()) - min(self.contents.keys()) + 1

best = 1000000
best_mob = None
in_weights = list(map(int,input().split()))
time.clock()
while time.clock() < 5:
    mob = Mobile()
    for insert in random.sample(in_weights, len(in_weights)):
        mob.add(insert, mob.choose_point(insert))
    if not mob.lean:
        if mob.score() < best:
            best = mob.score()
            best_mob = mob

print("Best mobile found, score %d:" % best_mob.score())
print(best_mob)

Єдине з цих рішень, яке, на мою думку, є неоптимальним - це найдовше рішення, яке має це рішення, яке я знайшов після 10-хвилинної пробіжки:

Best mobile found, score 60:
                   |                   
+-+-+-+-+-+-+-+-+-+++-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | | | | |
3 2 9 4 7 8 1 6 9 8 7 1 6 2 4 5 7 3 5 7
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.