Bangun ponsel kecil dan seimbang

18

Anda diberi banyak bobot, dan tugas Anda adalah membangun ponsel seimbang kecil menggunakan bobot itu.

Input adalah daftar bobot bilangan bulat dalam rentang 1 hingga 9, inklusif. Mungkin ada duplikat.

Outputnya adalah gambar ascii dari sebuah ponsel yang, ketika digantung, akan menyeimbangkan. Mungkin yang terbaik ditunjukkan oleh contoh:

memasukkan

3 8 9 7 5

kemungkinan keluaran

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

Anda harus menggunakan karakter ascii seperti yang ditunjukkan. Segmen horisontal dan vertikal dapat memiliki panjang berapa pun. Tidak ada bagian dari ponsel yang dapat menyentuh (horizontal atau vertikal) bagian lain dari ponsel yang tidak terhubung. Semua bobot harus digantung dari segmen vertikal yang panjangnya minimal 1, dan harus ada segmen vertikal tempat seluruh ponsel digantung.

Ukuran mobile adalah jumlah total +, -dan |karakter yang dibutuhkan untuk membangunnya. Ukuran yang lebih rendah lebih baik.

Anda dapat menempatkan sebanyak mungkin koneksi di segmen yang Anda inginkan. Sebagai contoh:

memasukkan

2 3 3 5 3 9

kemungkinan keluaran

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

Program pemenang adalah program yang dapat menghasilkan rata-rata ukuran ponsel terendah untuk serangkaian input uji. Tes sebenarnya adalah super rahasia untuk mencegah pengkodean-keras, tetapi akan menjadi sesuatu seperti ini:

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
Keith Randall
sumber
Fisika juga terlibat?
KAMU
1
@ S.Mark: Saya kira Anda bisa mengatakan itu. Untuk gangguan fisik, jumlah total_weight_hung_from_point * distance_of_point_from_pivotharus sama di kedua sisi titik pivot.
Keith Randall
Mungkin untuk mempermudah memeriksa diagram, membuatnya sedemikian sehingga satu bilah sama dengan sekitar dua tanda hubung? Seperti berdiri, diagram Anda terlihat tidak seimbang.
Thomas O

Jawaban:

5

Python 2.

Saya sedikit curang :

  • Saya hanya membuat ponsel dengan satu horisontal. Aku punya perasaan (tapi saya belum membuktikannya) bahwa optimal ponsel di bawah kondisi yang diberikan sebenarnya selalu tidak hanya memiliki satu horisontal. Sunting: Tidak selalu benar; dengan 2 2 9 1Nabb telah menemukan contoh balasan dalam komentar di bawah:

    Size 18:                Size 16:
       |                        |
    +-++--+-----+            +--++-+
    | |   |     |            |   | |
    2 9   2     1           -+-  9 1
                            | |
                            2 2
    
  • Saya hanya melakukan tindakan brute-force:

    1. Bobot yang diberikan dikocok secara acak.
    2. Dua bobot pada satu waktu ditempatkan pada ponsel di posisi terbaik sehingga tetap seimbang.
    3. Jika ponsel yang dihasilkan lebih baik daripada yang pernah kami miliki sebelumnya, ingatlah.
    4. Bilas dan ulangi, sampai jumlah detik yang ditentukan sudah habis.

Hasil saya untuk input sampel Anda; masing-masing dijalankan selama 5 detik (saya sadar ini konyol untuk yang kecil - hanya melalui semua permutasi yang mungkin akan lebih cepat). Perhatikan bahwa karena ada elemen acak, proses selanjutnya dapat menemukan hasil yang lebih baik atau lebih buruk.

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

Kode (verbose, karena ini bukan kode golf):

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()
balpha
sumber
@Nabb: Bobot lebih tinggi dari 9 tidak mungkin. Adapun 1 9 2 8itu menghasilkan 1-------8+-9--2; dari atas kepala saya, saya tidak dapat menemukan sesuatu yang lebih baik (tapi saya tidak akan bergantung pada itu) - apakah Anda memiliki sesuatu?
balpha
1
@balpha: Nevermind, tidak berpikir jernih ketika saya berkomentar sebelumnya. Saya berpikir untuk beberapa alasan bahwa Anda bisa menjadikannya 1-9 dan 2-8, tetapi jelas pasangan itu sendiri tidak seimbang!
Nabb
Oke, ini salah satu yang mungkin sebenarnya lebih baik dengan banyak lapisan:, 2 2 9 1yaitu (2 + 2) * 3 = 9 + 1 * 3 untuk ukuran 16 daripada 2-9+--2----1yang 18. Saya kira ada ambang (mungkin 5 atau 6 ) setelah itu satu baris horisontal selalu optimal.
Nabb
@Nabb: Yap; itu memang contoh yang bagus.
balpha
@Nabb, Bar tunggal dengan 2-2-+9-1saldo, dengan skor 13 (4*2+2*2 = 9*1+1*3). Jadi saya tidak berpikir bahwa itu adalah contoh tandingan yang baik.
Keith Randall
1

Yah ini adalah pertanyaan lama, tapi saya baru saja melihatnya muncul di tab pertanyaan atas jadi inilah solusi (optimal) saya:

#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;
}

Dari melihat aturan saya cukup yakin itu tidak curang, meskipun rasanya seperti itu. Ini hanya akan menampilkan semua angka yang diberikan dalam rantai vertikal, dengan total biaya 2 * number_of_inputs (yang merupakan jumlah minimum yang mungkin karena setiap angka harus memiliki bilah di atasnya, apa pun tata letaknya). Ini sebuah contoh:

./mobile 3 8 9 7 5

Menghasilkan:

|
3
|
8
|
9
|
7
|
5

Yang tentu saja dalam keseimbangan sempurna.


Saya awalnya akan mencoba sesuatu yang lebih dalam semangat tantangan ini, tetapi ternyata dengan cepat ia hanya dioptimalkan untuk struktur ini saja.

Dave
sumber
Mungkin tidak jelas dari deskripsi saya, tetapi Anda tidak dapat menghubungkan |ke bagian bawah berat.
Keith Randall
@KeithRandall ah ok; dengan itu dalam pikiran saya mungkin harus mencoba menyelesaikan ini dengan benar.
Dave
1

Berikut adalah solusi yang memaksa solusi baris tunggal terkecil. Kode ini mengulangi semua permutasi dan menghitung pusat massa masing-masing. Jika pusat massa memiliki koordinat bilangan bulat, kami telah menemukan solusinya.

Setelah semua permutasi telah dicoba, kami menambahkan segmen ke campuran (setara dengan berat massa 0) di set bobot dan percobaan ulang kami saat ini.

Untuk menjalankan program, lakukan 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)

yang menghasilkan solusi terbaik ini:

    |
+-+-+-+-+
| | | | |
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
olivieradam666
sumber
0

Python 3

Ini tidak lebih buruk dari 1 lebih dari optimal pada salah satu kasus uji, saya percaya, dan melakukannya dalam 5 detik.

Pada dasarnya, saya menggunakan pendekatan bar tunggal. Saya memesan input secara acak, lalu memasukkan bobot ke bilah satu per satu. Setiap elemen diletakkan pada posisi yang meminimalkan kelebihan berat di kedua sisi, atau posisi terbaik kedua dari perspektif itu, menggunakan 75% sebelumnya dan 25% terakhir. Lalu, saya memeriksa apakah ponsel seimbang pada akhirnya, dan lebih baik daripada ponsel terbaik yang ditemukan sejauh ini. Saya menyimpan yang terbaik, kemudian berhenti dan mencetaknya setelah 5 detik pencarian.

Hasil, dalam 5 detik berjalan:

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

Kode:

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)

Satu-satunya solusi yang saya percaya adalah suboptimal adalah yang terpanjang, yang memiliki solusi ini, yang saya temukan setelah berjalan 10 menit:

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
isaacg
sumber