Проблема з оптимізацією із дивними монетами


17

У вас є nмонети, кожна з яких важить або -1, або 1. Кожна маркується від, 0щоб n-1ви могли розпізнати монети. У вас також є один (магічний) ваговий пристрій. На першому кроці ви можете покласти стільки монет, скільки вам подобається, на ваговий пристрій, який здатний вимірювати як негативні, так і позитивні ваги, і він точно підкаже, скільки вони важать.

Однак у ваговому пристрої є щось дійсно дивне. Якщо ви x_1, x_2, ..., x_jвперше помістите монети на пристрій, то наступного разу вам доведеться ставити монети (x_1+1), (x_2+1) , ..., (x_j+1)в масштабі за винятком того, що ви, звичайно, не можете покласти монету, пронумеровану вище n-1. Мало того, що для кожного нового зважування ви вибираєте, чи хочете ви також покласти монету 0на шкалу.

За цим правилом, яка найменша кількість зважувань, яка завжди скаже вам, які монети важать 1, а які - -1?

Зрозуміло, що ви можете просто покласти монету 0на пристрій у першу чергу, і тоді знадобиться рівно nзважування, щоб вирішити проблему.

Мови та бібліотеки

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

Оцінка

Для даної nвашої оцінки nділиться на кількість зважувань, які вам потрібні в гіршому випадку. Тому більш високі бали є кращими. У цю головоломку немає ніякого вкладу, але ваша мета - знайти те, nза яке ви зможете отримати найвищий бал.

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

Завдання

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

Провідні записи

  • 4/3 7/5 у Python від Sarge Borsch
  • 26/14 на Яві Пітер Тейлор

8
Я хотів би взяти в руки деякі антигравітаційні монети.
mbomb007

2
У мене є рішення, яке ніколи не використовує автомат: Тримайте кожну монету і дивіться, які витягніть руку вгору, а які - тягніть руку вниз.
Фонд позову Моніки

1
Крім того, в якості побічної записки може бути краще написати "якщо ви зважуєте монети через" b ", то наступного разу вам доведеться робити від 1 до b + 1" (можливо, з "принаймні", кинутим теж, і краще форматування) замість підписок, що позначають номер монети. Це робить здається, що це якась властивість чи кількість монети _ замість самої монети.
Фонд позову Моніки

1
@ mbomb007 При кожному зважуванні ви можете зважити монету 0, а також всі інші монети, які ви будете зважувати. Іншими словами, у вас є новий вибір для кожного зважування.

3
@ mbomb007 @QPaysTaxes Щодо позначення x_i: ми можемо мати, наприклад, перше зважування (x_1, x_2, x_3) = (3, 2, 7), а потім друге зважування може бути або (4, 3, 8) або ( 0, 4, 3, 8). Етикетки монети не повинні бути послідовними, а індекс iв x_iне посилається на етикетку монети.
Мітч Шварц

Відповіді:


3

C ++, оцінка 23/12 25/13 27/14 28/14 = 2 31/15

Рішення властивості Matrix X, переглянуті (або Радість X) , прямо використовуються як рішення цієї проблеми. Наприклад розчин 31 ряду 15 стовпців:

1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1 1 0 1 1 0 
1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1 1 0 1 1 
1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1 1 0 1 
1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1 1 0 
1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1 1 
0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1 
0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 
1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 
0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 
0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 
0 0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 
1 0 0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 
0 1 0 0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 
0 0 1 0 0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 
1 0 0 1 0 0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 

рядок N представляє, які монети ви ставите на шкалі для вимірювання N. Якими б не були результати зважування, очевидно, є певний набір монетних значень, який дає цю вагу. Якщо є й інша комбінація (рішення не унікальне), подумайте, чим вони відрізняються. Ви повинні замінити набір зважування 1монет на зважування монет -1. Це дає набір стовпців, які відповідають цьому фліп. Існує також набір ваг монет, -1які ви замінюєте саме такими матрицями, де таких наборів стовпців не існує, тому дублікатів немає і кожне рішення є унікальним.1 . Це ще один набір стовпців. Оскільки вимірювання не змінюються між двома рішеннями, це означає, що сума стовпців двох наборів повинна бути однаковою. Але рішення матриці властивості X переглянуті (або Радість X)

Кожен фактичний набір вимірювань може бути описаний якоюсь 0/1матрицею. Але навіть якщо деякі набір стовпців дорівнюють одним і тим же векторам, може бути, що знаки значень монети рішення рішення точно не відповідають такому набору. Тому я не знаю, чи є матриці на зразок наведеної вище. Але принаймні вони забезпечують нижню межу. Тож можливість, що 31 монету можна зробити менше, ніж 15 вимірювань, все ще відкрита.

Зауважте, що це справедливо лише для нефіксованої стратегії, коли ваше рішення поставити монету 0на ваги залежить від результату попереднього зважування. Інакше у вас будуть рішення, де знаки монет відповідають наборам, що мають однакову суму стовпців.


Поточний світовий рекорд :)

Наскільки швидко, на вашу думку, потрібен комп'ютер, щоб досягти 2?

@Lembik Я не впевнений, що 2 можливо. Я не знаю чому, але теперішні результати підказують, що ви можете наблизитись до 2 довільно закрити, не доходячи до нього
Тонна Евангелія

Чи отримали ви можливість перевірити циркуляційну матрицю 25 на 50, яку я вставив, яка повинна дати 2? 01011011100010111101000001100111110011010100011010 як перший ряд матриці циркуляції.

Я не знаю, як навіть перевірити цю матрицю, не написавши спеціальної програми, яка працюватиме тривалий час
Ton Hospel

5

Python 2, оцінка = 1,0

Це найпростіший бал, якщо ніхто не знайде кращий бал (сумнівний). nзважування для кожного n.

import antigravity
import random

def weigh(coins, indices):
    return sum(coins[i] for i in indices)

def main(n):
    coins = [random.choice([-1,1]) for i in range(n)]
    for i in range(len(coins)):
        print weigh(coins, [i]),

main(4)

Я імпортував, antigravityщоб програма могла працювати з негативними вагами.


Дуже корисний. Дякую :)

Імпорт antigravity- це в основному неоперативний, правда?
Відображуване ім’я

@SargeBorsch Для цієї програми він є. Але насправді щось робить.
mbomb007

5

Оцінка = 26/14 ~ = 1,885

import java.util.*;

public class LembikWeighingOptimisation {

    public static void main(String[] args) {
        float best = 0;
        int opt = 1;
        for (int n = 6; n < 32; n+=2) {
            long start = System.nanoTime();
            System.out.format("%d\t", n);
            opt = optimise(n, n / 2 + 1);
            float score = n / (float)opt;
            System.out.format("%d\t%f", opt, score);
            if (score > best) {
                best = score;
                System.out.print('*');
            }
            System.out.format(" in %d seconds", (System.nanoTime() - start) / 1000000000);
            System.out.println();
        }
    }

    private static int optimise(int numCoins, int minN) {
        MaskRange.N = numCoins;
        Set<MaskRange> coinSets = new HashSet<MaskRange>();
        coinSets.add(new MaskRange(0, 0));

        int allCoins = (1 << numCoins) - 1;

        for (int n = minN; n < numCoins; n++) {
            for (int startCoins = 1; startCoins * 2 <= numCoins; startCoins++) {
                for (int mask = (1 << startCoins) - 1; mask < (1 << numCoins); ) {
                    // Quick-reject: in n turns, do we cover the entire set?
                    int qr = (1 << (n-1)) - 1;
                    for (int j = 0; j < n; j++) qr |= mask << j;
                    if ((qr & allCoins) == allCoins && canDistinguishInNTurns(mask, coinSets, n)) {
                        System.out.print("[" + Integer.toBinaryString(mask) + "] ");
                        return n;
                    }

                    // Gosper's hack to update
                    int c = mask & -mask;
                    int r = mask + c;
                    mask = (((r^mask) >>> 2) / c) | r;
                }
            }
        }

        return numCoins;
    }

    private static boolean canDistinguishInNTurns(int mask, Set<MaskRange> coinsets, int n) {
        if (n < 0) throw new IllegalArgumentException("n");
        int count = 0;
        for (MaskRange mr : coinsets) count += mr.size();
        if (count <= 1) return true;
        if (n == 0) return false;

        // Partition.
        Set<MaskRange>[] p = new Set[Integer.bitCount(mask) + 1];
        for (int i = 0; i < p.length; i++) p[i] = new HashSet<MaskRange>();
        for (MaskRange range : coinsets) range.partition(mask, p);

        for (int d = 0; d < 2; d++) {
            boolean ok = true;
            for (Set<MaskRange> s : p) {
                if (!canDistinguishInNTurns((mask << 1) + d, s, n - 1)) {
                    ok = false;
                    break;
                }
            }

            if (ok) return true;
        }

        return false;
    }

    static class MaskRange {
        public static int N;
        public final int mask, value;

        public MaskRange(int mask, int value) {
            this.mask = mask;
            this.value = value & mask;
            if (this.value != value) throw new IllegalArgumentException();
        }

        public int size() {
            return 1 << (N - Integer.bitCount(mask));
        }

        public void partition(int otherMask, Set<MaskRange>[] p) {
            otherMask &= (1 << N) - 1;

            int baseline = Integer.bitCount(value & otherMask);
            int variables = otherMask & ~mask;
            int union = mask | otherMask;
            partitionInner(value, union, variables, baseline, p);
        }

        private static void partitionInner(int v, int m, int var, int baseline, Set<MaskRange>[] p) {
            if (var == 0) {
                p[baseline].add(new MaskRange(m, v));
            }
            else {
                int lowest = var & (1 + ~var);
                partitionInner(v,          m, var & ~lowest, baseline, p);
                partitionInner(v | lowest, m, var & ~lowest, baseline + 1, p);
            }
        }

        @Override
        public String toString() {
            return String.format("(x & %x = %x)", mask, value);
        }
    }
}

Зберегти як LembikWeighingOptimisation.java, скласти як javac LembikWeighingOptimisation.java, запустити як java LembikWeighingOptimisation.

Велике спасибі Мітчу Шварцу за вказівку на помилку в першій версії швидкого відхилення.

Тут використовуються деякі досить базові методи, які я не можу жорстко обґрунтувати. Це грубі сили, але лише для запуску зважувальних операцій, які використовують більшість половини монет: послідовності, в яких використовується більше половини монет, не підлягають прямому доповненню зважування (оскільки ми не знаємо загальної ваги), але на хвилястому рівні має бути приблизно однаковий обсяг інформації. Він також здійснює повторне зважування в порядку кількості залучених монет, виходячи з того, що таким чином він охоплює дисперговані зважування (які, сподіваємось, дають інформацію про верхній кінець порівняно рано), не попередньо проповзаючи через купу, яка починається з щільної підмножини на нижній кінець.

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

20      [11101001010] 11        1.818182* in 5364 seconds
22      [110110101000] 12       1.833333* in 33116 seconds
24      [1000011001001] 13      1.846154* in 12181 seconds                                                                                                            
26      [100101001100000] 14    1.857143* in 73890 seconds  

Ви точно не можете отримати 12/7? Я майже впевнений, що це працює. Крім того, як щодо 19/10? Я думав, що мій код дав мені це колись, але я не можу його відтворити зараз.

@Lembik, я перерахував 12/7, але найкраще, що я можу зробити для 19 - це 19/11.
Пітер Тейлор

О так вибачте. Чи можливі ваші евристичні відкидання якихось рішень? Я впевнений, що 19/10 теж повинен працювати.

Так, можливо , якщо єдине рішення має початкову вагу з більш ніж половиною монет. Я був би м'яко здивований, хоча.
Пітер Тейлор

Чи варто збільшити половину порогу до трохи більше, ніж на половину, можливо, просто побачити?

2

Python 3, оцінка = 4/3 = 1,33… (N = 4) оцінка = 1,4 (N = 7)

Оновлення: впроваджено брутальний пошук у наборі «статичних» рішень та отримав новий результат

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

Ось код Python, який здійснює пошук у всіх статичних розв'язках для малих n значень (ці розв'язувачі завжди зважують однакові набори монет, отже, "статичне" ім'я) та визначають їх найгірший випадок, просто перевіряючи, чи дозволяють їх результати вимірювання лише одну збіжну монету встановити у всіх випадках. Крім того, він відслідковує найкращі результати, знайдені до цього часу, і ранні розсіви чорносливу, які показали, що вони, безумовно, гірші, ніж ті, що були знайдені раніше. Це була важлива оптимізація, інакше я не міг дочекатися цього результатуn = 7. (Але це явно все ще не дуже добре оптимізовано)

Сміливо задайте питання, якщо не зрозуміло, як це працює ...

#!/usr/bin/env python3
import itertools
from functools import partial


def get_all_possible_coinsets(n):
    return tuple(itertools.product(*itertools.repeat((-1, 1), n)))


def weigh(coinset, indexes_to_weigh):
    return sum(coinset[x] for x in indexes_to_weigh)


# made_measurements: [(indexes, weight)]
def filter_by_measurements(coinsets, made_measurements):
    return filter(lambda cs: all(w == weigh(cs, indexes) for indexes, w in made_measurements), coinsets)


class Position(object):
    def __init__(self, all_coinsets, coinset, made_measurements=()):
        self.all_coinsets = all_coinsets
        self.made_measurements = made_measurements
        self.coins = coinset

    def possible_coinsets(self):
        return tuple(filter_by_measurements(self.all_coinsets, self.made_measurements))

    def is_final(self):
        possible_coinsets = self.possible_coinsets()
        return (len(possible_coinsets) == 1) and possible_coinsets[0] == self.coins

    def move(self, measurement_indexes):
        measure_result = (measurement_indexes, weigh(self.coins, measurement_indexes))
        return Position(self.all_coinsets, self.coins, self.made_measurements + (measure_result,))


def get_all_start_positions(coinsets):
    for cs in coinsets:
        yield Position(coinsets, cs)


def average(xs):
    return sum(xs) / len(xs)


class StaticSolver(object):
    def __init__(self, measurements):
        self.measurements = measurements

    def choose_move(self, position: Position):
        index = len(position.made_measurements)
        return self.measurements[index]

    def __str__(self, *args, **kwargs):
        return 'StaticSolver({})'.format(', '.join(map(lambda x: '{' + ','.join(map(str, x)) + '}', self.measurements)))

    def __repr__(self):
        return str(self)


class FailedSolver(Exception):
    pass


def test_solvers(solvers, start_positions, max_steps):
    for solver in solvers:
        try:
            test_results = tuple(map(partial(test_solver, solver=solver, max_steps=max_steps), start_positions))
            yield (solver, max(test_results))
        except FailedSolver:
            continue


def all_measurement_starts(n):
    for i in range(1, n + 1):
        yield from itertools.combinations(range(n), i)


def next_measurement(n, measurement, include_zero):
    shifted = filter(lambda x: x < n, map(lambda x: x + 1, measurement))
    if include_zero:
        return tuple(itertools.chain((0,), shifted))
    else:
        return tuple(shifted)


def make_measurement_sequence(n, start, zero_decisions):
    yield start
    m = start
    for zero_decision in zero_decisions:
        m = next_measurement(n, m, zero_decision)
        yield m


def measurement_sequences_from_start(n, start, max_steps):
    continuations = itertools.product(*itertools.repeat((True, False), max_steps - 1))
    for c in continuations:
        yield tuple(make_measurement_sequence(n, start, c))


def all_measurement_sequences(n, max_steps):
    starts = all_measurement_starts(n)
    for start in starts:
        yield from measurement_sequences_from_start(n, start, max_steps)


def all_static_solvers(n, max_steps):
    return map(StaticSolver, all_measurement_sequences(n, max_steps))


def main():
    best_score = 1.0
    for n in range(1, 11):
        print('Searching with N = {}:'.format(n))
        coinsets = get_all_possible_coinsets(n)
        start_positions = tuple(get_all_start_positions(coinsets))


        # we are not interested in solvers with worst case number of steps bigger than this
        max_steps = int(n / best_score)

        solvers = all_static_solvers(n, max_steps)
        succeeded_solvers = test_solvers(solvers, start_positions, max_steps)

        try:
            best = min(succeeded_solvers, key=lambda x: x[1])
        except ValueError:  # no successful solvers
            continue
        score = n / best[1]
        best_score = max(score, best_score)
        print('{}, score = {}/{} = {}'.format(best, n, best[1], score))
    print('That\'s all!')


def test_solver(start_position: Position, solver, max_steps):
    p = start_position
    steps = 0
    try:
        while not p.is_final():
            steps += 1
            if steps > max_steps:
                raise FailedSolver
            p = p.move(solver.choose_move(p))
        return steps
    except IndexError:  # solution was not found after given steps — this solver failed to beat score 1
        raise FailedSolver


if __name__ == '__main__':
    main()

Вихід:

Searching with N = 1:
(StaticSolver({0}), 1), score = 1/1 = 1.0
Searching with N = 2:
(StaticSolver({0}, {0,1}), 2), score = 2/2 = 1.0
Searching with N = 3:
(StaticSolver({0}, {0,1}, {0,1,2}), 3), score = 3/3 = 1.0
Searching with N = 4:
(StaticSolver({0,1}, {1,2}, {0,2,3}, {0,1,3}), 3), score = 4/3 = 1.3333333333333333
Searching with N = 5:
Searching with N = 6:
Searching with N = 7:
(StaticSolver({0,2}, {0,1,3}, {0,1,2,4}, {1,2,3,5}, {0,2,3,4,6}), 5), score = 7/5 = 1.4
Searching with N = 8:
Searching with N = 9:
(I gave up waiting at this moment)

Цей рядок (StaticSolver({0,2}, {0,1,3}, {0,1,2,4}, {1,2,3,5}, {0,2,3,4,6}), 5), score = 7/5 = 1.4розкриває найкраще знайдене рішення. Цифри в {}дужках - це індекси монет, які слід наносити на зважувальний пристрій на кожному кроці.


4
PS Я писав це, поки джерело електроенергії в моєму будинку було порушено, тому у мене був ноутбук, який жив від акумулятора і не було підключення до Інтернету, і мені просто не було нічого кращого, ніж зламати деякі головоломки. Напевно, я б не потурбувався, якщо все було добре: D
Відображати ім'я
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.