Garip paralarla optimizasyon zorluğu


17

You have nher iki tartmak -1 veya 1. Her gelen etiketli paralar 0için n-1size birbirinden paraları söyleyebilir böylece. Bir (sihirli) tartım cihazınız da var. İlk dönüşte, hem negatif hem de pozitif ağırlıkları ölçebilen tartım cihazına istediğiniz kadar çok para koyabilirsiniz ve size tam olarak ne kadar ağır olduklarını söyleyecektir.

Bununla birlikte, tartım cihazı hakkında gerçekten garip bir şey var. x_1, x_2, ..., x_jCihaza ilk kez para koyarsanız , bir dahaki sefere (x_1+1), (x_2+1) , ..., (x_j+1), elbette daha yüksek numaralı bir para koyamayacağınız istisna olmak üzere, ölçeğe para koymanız gerekir n-1. Sadece bu değil, her yeni tartı 0için de teraziye para koymak isteyip istemediğinizi seçebilirsiniz .

Bu kural uyarınca, her zaman tam olarak hangi sikkelerin 1, hangilerinin -1 ağırlığında olduğunu söyleyecek en az sayıda tartım nedir?

Açıkça 0, ilk dönüşte cihaza para koyabilirsiniz ve sonra nsorunu çözmek için tam olarak tartım gerekir.

Diller ve kütüphaneler

İstediğiniz herhangi bir dili veya kütüphaneyi kullanabilirsiniz (bu zorluk için tasarlanmamıştır). Ancak, eğer çok takdir edilecektir Ubuntu içinde çalıştırmak için nasıl net talimatlar sağlayabilir eğer mümkünse kodunuzu test etmek istiyorum.

Puan

Belirli bir npuan niçin en kötü durumda ihtiyacınız olan ağırlık sayısına bölünür. Bu nedenle daha yüksek puanlar daha iyidir. Bu bulmacanın girdisi yok ama amacınızn en yüksek puanı alabileceğiniz bir puan bulmak.

Beraberlik olursa ilk cevap kazanır. Birinin sonsuz puan almanın bir yolunu bulduğu son derece düşük bir durumda, o kişi hemen kazanır.

Görev

Senin görevin sadece en yüksek puanı alan kodu yazmaktır. Kodunuzun hem akıllıca bir seçim yapması hem de bunun için tartım sayısını optimize etmesi gerekir n.

Öncü girişler

  • Python içinde 4/3 7/5 tarafından Sarge Borsch
  • İçinde 26/14 Java Peter Taylor tarafından

8
Ellerimi yerçekimi karşıtı paralar üzerine getirmeyi çok isterim.
mbomb007

2
Makineyi hiç kullanmayan bir çözümüm var: Her madeni parayı tutun ve hangilerinin elinizi yukarı ve hangilerinin aşağı çektiğini görün.
Monica'nın Davası

1
Ayrıca, bir yan not olarak, "a'dan b'ye kadar paralar tartarsanız, bir dahaki sefere bir + 1'den b + 1'e yapmanız gerektiğinde" yazmak daha iyi olabilir (belki de 'en azından' atılmış, ve daha iyi biçimlendirme) jeton numarasını gösteren abonelikler yerine. Bu, madalyonun kendisi yerine madalyonun bir özelliği veya miktarı gibi görünmesini sağlar.
Monica'nın Davası

1
@ mbomb007 Her tartıda, 0 parasının yanı sıra tartacağınız diğer tüm paraları da tartmayı seçebilirsiniz. Başka bir deyişle, yaptığınız her tartım için yeni bir seçeneğiniz vardır.

3
Gösterimle ilgili olarak x_i: Örneğin ilk tartımız (x_1, x_2, x_3) = (3, 2, 7) olabilir ve sonra ikinci tartı (4, 3, 8) veya ( 0, 4, 3, 8). Sikke etiketleri ardışık olması gerekmez ve endeks iiçinde x_imadalyonun etikete ifade etmez.
Mitch Schwartz

Yanıtlar:


3

C ++, Puan 23/12 25/13 27/14 28/14 = 2 31/15

Yeniden gözden geçirilen (veya X Sevinci) Matrix özelliği X'in çözümleri doğrudan bu soruna çözüm olarak kullanılabilir. Örneğin 31 sıra 15 sütunun çözümü:

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 satırı, ölçüm için ölçeğe hangi paraları koyduğunuzu temsil eder. Aldığınız ağırlığın sonuçları ne olursa olsun, bu ağırlığı veren bazı jeton değerleri seti vardır. Başka bir kombinasyon varsa (çözüm benzersiz değildir) nasıl farklı olduklarını düşünün. Sen ağırlıklandırma paralar bir dizi değiştirmelisiniz 1ağırlıklandırılarak sikke tarafından -1. Bu, o çevirmeye karşılık gelen bir dizi sütun verir. Ayrıca, -1değiştirdiğiniz madeni paraların ağırlığı da vardır 1. Bu başka bir sütun kümesidir. Ölçümler iki çözüm arasında değişmediği için iki kümenin sütun toplamları aynı olmalıdır. Ancak, çözümler Matrix X özelliği tekrar ziyaret edildi (veya X Sevinci) tam olarak bu tür sütun kümelerinin bulunmadığı matrislerdir, bu nedenle kopya yoktur ve her çözüm benzersizdir.

Her bir gerçek ölçüm seti bazı 0/1matrislerle açıklanabilir . Ancak, bazı sütun kümeleri aynı vektörleri toplasa bile, aday çözüm madeni para değerlerinin işaretleri böyle bir kümeye tam olarak karşılık gelmeyebilir. Bu yüzden yukarıdaki gibi matrislerin optimal olup olmadığını bilmiyorum. Ama en azından bir alt sınır sağlıyorlar. Böylece 15 jetonun 15 ölçümden daha az bir sürede yapılabilmesi hala açıktır.

Bunun yalnızca 0ölçeklere madeni para koyma kararınızın önceki ağırlıkların sonucuna bağlı olduğu sabit olmayan bir strateji için geçerli olduğuna dikkat edin . Aksi takdirde , madeni paraların işaretlerinin aynı sütun toplamına sahip setlere karşılık geldiği çözümlere sahip olacaksınız .


Güncel Dünya Rekoru :)

2'ye ulaşmak için ne kadar hızlı bir bilgisayara ihtiyaç olacağını tahmin ediyorsunuz?

@Lembik İkna olmadım 2 mümkün. Nedenini bilmiyorum, ancak mevcut sonuçlar 2'ye hiç ulaşmadan keyfi olarak yaklaşabileceğinizi gösteriyor
Ton Hospel

Yapıştırdığım 25'e 50 dolaşım matrisini 2 vermesi gerektiğini doğrulama şansınız oldu mu? 01011011100010111101000001100111110011010100011010 bir sirkülasyon matrisinin ilk satırı olarak.

Uzun süre çalışacak özel bir program yazmadan bu matrisi nasıl kontrol edeceğimi bilmiyorum
Ton Hospel

5

Python 2, skor = 1.0

Hiç kimse daha iyi bir puan bulamazsa (şüpheli) bu kolay skor. nher biri için ağırlıklar 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)

antigravityProgramın negatif ağırlıklarla çalışabilmesi için içe aktardım.


Çok yararlı. Teşekkür ederim :)

İçe aktarma antigravitytemelde bir işlem değildir, değil mi?
Görünen Ad

@SargeBorsch Bu programın amacı budur. Ama aslında bir şey yapıyor.
mbomb007

5

Puan = 26/14 ~ = 1.857

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

Farklı kaydet LembikWeighingOptimisation.java, farklı derle javac LembikWeighingOptimisation.java, farklı çalıştır java LembikWeighingOptimisation.

Hızlı reddetmenin ilk versiyonunda bir hataya işaret ettiği için Mitch Schwartz'a çok teşekkürler .

Bu, titizlikle haklı çıkaramayacağım bazı temel teknikleri kullanıyor. Kaba kuvvetler, ancak sadece madeni paraların en fazla yarısını kullanan tartım işlemlerine başlamak için: madeni paraların yarısından fazlasını kullanan diziler, tamamlayıcı tartımlara doğrudan aktarılamaz (çünkü toplam ağırlığı bilmiyoruz), ancak el dalgalı bir seviyede yaklaşık aynı miktarda bilgi olmalıdır. Aynı zamanda, ilk önce yoğun bir alt kümeyle başlayan bir demetten sürünmeden dağıtılmış tartımları (umarım üst uç hakkında nispeten erken bilgi verir) kapladığı temelde, ilgili madeni paraların sırasına göre tartımlara başlayarak tekrarlar. alt uç.

MaskRangeSınıf bellek kullanımı açısından önceki sürümünde büyük bir gelişme olduğunu ve bir darboğaz olmaktan GC kaldırır.

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  

Kesinlikle 12/7 alamıyor musunuz? Bunun işe yaradığından eminim. Ayrıca, 19/10? Kodumun bana bunu bir kez verdiğini sanıyordum ama şimdi tekrarlayamıyorum.

@Lembik, 12/7 listeledim, ancak 19 için yapabileceğim en iyi şey 19/11.
Peter Taylor

Ah evet üzgünüm. Buluşsal yönteminiz bazı çözümleri atmak mümkün mü? 19/10'un da çalışması gerektiğinden eminim.

Bu var olası tek çözüm varsa evet, bir ilk daha sikke yarıdan fazlası ile tartı. Yine de biraz şaşıracaktım.
Peter Taylor

Yarım eşiği, sadece görmek için yarıdan biraz daha fazlaya yükseltmeye değer mi?

2

Python 3, skor = 4/3 = 1.33… (N = 4) skor = 1.4 (N = 7)

Güncelleme: "statik" çözücüler kümesinde kaba kuvvet araması uygulandı ve yeni bir sonuç aldı

Daha ileri kararlar için ağırlıklandırma sonuçlarını kullanabilen dinamik çözücüler arayarak daha da geliştirilebileceğini düşünüyorum.

İşte tüm statik çözücülerden küçük n değerler için arama yapan bir Python kodu (bu çözücüler her zaman aynı jeton setlerini, dolayısıyla "statik" adını tartar) ve ölçüm sonuçlarının yalnızca bir eşleşen jetona izin verdiğini kontrol ederek en kötü adım sayısını belirler. her durumda ayarlanır. Ayrıca, şimdiye kadar bulunan en iyi skoru ve daha önce bulunanlardan kesinlikle daha kötü olduklarını gösteren erken erik çözücüleri takip ediyor. Bu önemli bir optimizasyon, aksi takdirde bu sonucu bekleyemedimn = 7 . (Ama yine de çok iyi optimize edilmedi)

Nasıl çalıştığı belli değilse soru sormaktan çekinmeyin…

#!/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()

Çıktı:

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)

Bu hat (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, bulunan en iyi çözücüyü ortaya çıkarır. {}Parantez içindeki sayılar , her adımda ağırlıklandırma cihazına konacak madeni para endeksleridir.


4
PS: Bunu evimdeki elektrik kaynağı kesilirken yazdım, bu yüzden pil gücü ve internet bağlantısı olmayan bir dizüstü bilgisayarım vardı ve yapmam gereken bazı bulmacaları kırmaktan daha iyi bir şeyim yoktu. Her şey yolunda olsaydı rahatsız olmazdım sanırım: D
Görünen Ad
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.