奇妙なコインによる最適化の課題


17

あなたは持っているn、それぞれがそれぞれのからラベル付けされているいずれかの重量を量る-1または1コイン0n-1あなたが離れてコインを伝えることができるようにします。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ます。

主要なエントリー

  • Sarge BorschによるPythonの4/3 7/5
  • ピーターテイラーによるJavaの 26/14

8
いくつかの反重力コインを手に入れたいです。
mbomb007

2
マシンを使用しないソリューションがあります。各コインを持ち、どのコインがあなたの手を引き上げ、どのコインがあなたの手を引き下げるかを確認します。
ファンドモニカの訴訟

1
また、サイドノートとして、「コインaからbの重さを量ってから、次にa + 1からb + 1をしなければならないとき」と書いた方が良いかもしれません(「少なくとも」を投げて、コイン番号を示す添え字の代わりに、より良いフォーマット)。それは、コインそのものではなく、コインのプロパティまたは量_のように見えます。
基金モニカの訴訟

1
@ mbomb007計量するたびに、コイン0と、計量する他のすべてのコインの重量を選択できます。言い換えれば、計量するたびに新しい選択をすることができます。

3
@ mbomb007 @QPaysTaxes表記に関してx_i:たとえば、(x_1、x_2、x_3)=(3、2、7)の最初の重み付けを行い、次に2番目の重み付けを(4、3、8 )または( 0、4、3、8)。コインのラベルは連続している必要はなく、インデックスiin x_iはコインのラベルを指すものではありません。
ミッチシュワルツ

回答:


3

C ++、スコア23/12 25/13 27/14 28/14 = 2 31/15

マトリックスプロパティXのソリューション(またはJoy of 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。これは別の列セットです。2つのソリューション間で測定値が変化しないため、2つのセットの列の合計は同じでなければなりません。しかし、MatrixプロパティXのソリューションが再検討されました(またはJoy of X) そのような列セットが存在しない正確にこれらのマトリックスであるため、重複はなく、各ソリューションは一意です。

測定の実際の各セットは、いくつかの0/1マトリックスで記述できます。ただし、一部の列セットの合計が同じベクトルであっても、候補コインのコイン値の符号がそのようなセットに正確に対応していない可能性があります。したがって、上記のような行列が最適かどうかはわかりません。しかし、少なくとも下限は提供されます。そのため、15個未満の測定で31個のコインを処理できる可能性はまだありません。

これは0、スケールにコインを入れる決定が以前の重み付けの結果に依存する非固定戦略にのみ当てはまることに注意してください。そうしないと、しますコインの兆候が同じ列の合計を持ってセットに対応したソリューションを持っています。


現在の世界記録:)

2に到達するのにコンピューターはどれくらいの速さで見積もられますか?

@Lembik 2が可能だとは思いません。私は理由を知りませんが、現在の結果は、あなただけの今までそれに到達することなく任意に近い2に近づくことができる示唆
トンHospel

貼り付けた25 x 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.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);
        }
    }
}

名前を付けて保存LembikWeighingOptimisation.java、名前を付けてコンパイルjavac LembikWeighingOptimisation.java、実行java LembikWeighingOptimisation

クイック拒否の最初のバージョンのバグを指摘してくれたMitch Schwartzに感謝します。

これは、私が厳密に正当化できないいくつかのかなり基本的なテクニックを使用しています。ブルートフォースですが、コインの最大半分を使用する計量操作を開始する場合のみ:コインの半分以上を使用するシーケンスは、補完的な計量に直接転送できません(合計重量がわからないため)しかし、手で波打つレベルでは、ほぼ同じ量の情報があるはずです。また、最初に密集したサブセットで始まるバンチをクロールせずに、分散計量をカバーすることに基づいて、関連するコインの数の順番で計量を開始することで反復します(これは、比較的早期に上端に関する情報を提供することが望ましい)下端。

この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)

更新:「静的」ソルバーセットにブルートフォース検索を実装し、新しい結果を得ました

動的なソルバーを検索することでさらに改善できると思います。動的なソルバーは、さらに結果を決定するために重み付けの結果を使用できます。

これは、すべての静的ソルバーで小さな n値を検索するPythonコードです(これらのソルバーは常に同じコインセットの重量を測定するため、「静的」名)。測定結果が一致するコインを1つだけ許可することをチェックするだけで、最悪の場合のステップ数を決定しますすべての場合に設定します。また、これまでに見つかったベストスコアと、以前に見つかったものよりも明らかに悪いことを示した初期のプルーンソルバーを追跡します。これは重要な最適化でした。それ以外の場合、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
表示名
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.