이상한 동전으로 최적화 도전


17

당신은 n각각 하나의 무게 -1 또는 1 각이에서 레이블이 동전 0n-1당신이 떨어져 동전을 알 수 있도록합니다. 하나의 (마법) 계량 장치도 있습니다. 첫 번째 턴에서 계량 장치에 원하는만큼 많은 동전을 넣을 수 있습니다.이 장치는 음과 양의 무게를 모두 측정 할 수 있으며 정확히 얼마나 많은 무게인지 알려줍니다.

그러나 계량 장치에는 정말 이상한 점이 있습니다. x_1, x_2, ..., x_j기기에 처음으로 동전을 넣은 경우 다음에 동전 을 동전 위에 넣을 수는 (x_1+1), (x_2+1) , ..., (x_j+1)있지만 예외적으로 동전보다 큰 번호의 동전을 넣을 수는 없습니다 n-1. 뿐만 아니라 모든 새로운 계량 0에 대해 저울 에 동전을 넣을 것인지 선택할 수 있습니다 .

이 규칙에 따라, 정확히 1의 무게와 1의 무게를 정확히 알려주는 가장 적은 수의 무게가 무엇입니까?

분명히 당신은 동전을 넣을 수 있습니다 0 첫 번째 차례에 장치에 을n 수 있으며 문제를 해결하기 위해 정확히 무게를 측정해야합니다.

언어와 라이브러리

원하는 언어 나 라이브러리를 사용할 수 있습니다 (이 과제를 위해 설계되지 않았습니다). 그러나 가능한 경우 코드를 테스트하여 우분투에서 코드를 실행하는 방법에 대한 명확한 지침을 제공 할 수 있다면 대단히 감사하겠습니다.

점수

주어진 n점수는 n최악의 경우 필요한 계량 횟수로 나뉩니다. 따라서 높은 점수가 좋습니다. 이 퍼즐에는 입력이 없지만 목표는 n가장 높은 점수를 얻을 수있는를 찾는 것 입니다.

동점이 있으면 첫 번째 답이 이깁니다. 누군가가 무한한 점수를 얻는 방법을 찾은 극히 드문 상황에서는 그 사람이 즉시 이깁니다.

직무

당신의 임무는 단순히 가장 높은 점수를 얻는 코드를 작성하는 것입니다. 코드는 n을 영리하게 선택한 다음 그에 대한 계량 횟수를 최적화해야합니다.n .

주요 항목

  • 파이썬 에서 4/3 7/5Sarge Borsch의
  • Peter Taylor의 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)의 첫 번째 계량을 가질 수 있으며 두 번째 계량은 (4, 3, 8) 또는 ( 0, 4, 3, 8). 동전 라벨은 연속적 일 필요는 인덱스 i에이 x_i동전의 레이블을 참조하지 않습니다.
Mitch Schwartz

답변:


3

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

다시 방문한 Matrix 속성 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. 그것은 또 다른 열 집합입니다. 두 솔루션간에 측정 값이 변경되지 않기 때문에 두 세트의 열 합이 같아야합니다. 그러나 매트릭스 속성 X에 대한 솔루션은 다시 방문했습니다 (또는 X의 기쁨) 이러한 열 집합이 존재하지 않는 이러한 행렬이므로 정확하게 중복되지 않으며 각 솔루션은 고유합니다.

각 실제 측정 세트는 일부 0/1매트릭스 로 설명 할 수 있습니다 . 그러나 일부 열 집합이 동일한 벡터로 합쳐 지더라도 후보 솔루션 코인 값의 부호가 그러한 집합에 정확하게 일치하지 않을 수 있습니다. 따라서 위와 같은 행렬이 최적인지 모르겠습니다. 그러나 적어도 그들은 하한을 제공합니다. 따라서 15 개의 측정이 아직 열려 있지 않은 상태에서 31 개의 코인을 수행 할 수있는 가능성이 있습니다.

이것은 0저울 에 동전을 넣는 결정 이 이전 가중치의 결과에 의존하는 고정되지 않은 전략에 대해서만 적용됩니다 . 그렇지 않으면 동전의 부호가 동일한 열 합을 갖는 세트와 일치하는 솔루션 있습니다.


현재 세계 기록 :)

2에 도달하기 위해 얼마나 빠른 컴퓨터가 필요합니까?

@Lembik 나는 2가 가능하다고 확신하지 않습니다. 나는 왜 그런지 모르겠지만, 현재의 결과는 당신이 2에 접근하지 않고 임의로 2에 접근 할 수 있다는 것을 암시합니다
Ton Hospel

내가 붙여 넣은 25 x 50의 순환 행렬을 확인할 기회가 있었습니까? 순환 행렬의 첫 번째 행인 01011011100010111101000001100111110011010100011010

오랫동안 실행될 전용 프로그램을 작성하지 않고 매트릭스를 확인하는 방법조차 모릅니다
Ton Hospel

5

파이썬 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

파이썬 3, 점수 = 4/3 = 1.33… (N = 4) 점수 = 1.4 (N = 7)

업데이트 : "정적"솔버 세트에서 무차별 검색을 구현하고 새로운 결과를 얻었습니다.

추후 결정에 가중치 결과를 사용할 수있는 다이내믹 솔버를 검색하여 더 향상시킬 수 있다고 생각합니다.

다음은 작은 정적 n값 을 찾기 위해 모든 정적 솔버를 검색하는 Python 코드입니다 (이 솔버는 항상 동일한 코인 세트의 무게를가집니다. 따라서 "정적"이름). 측정 결과에 일치하는 코인 만 하나만 허용되는지 확인하여 최악의 단계 수를 결정합니다. 모든 경우에 설정됩니다. 또한, 지금까지 발견 된 최고의 점수와 자두 솔버가 이전에 발견 된 것보다 확실히 더 나쁘다는 것을 보여주었습니다. 이것은 중요한 최적화였습니다. 그렇지 않으면 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
추신 : 나는 집의 전원이 끊어졌을 때 이것을 썼다. 그래서 나는 배터리 전원과 인터넷 연결이없는 랩톱을 가지고 있었고, 퍼즐을 깨는 것보다 더 좋은 일이 없었습니다. 나는 모든 것이 괜찮다면 귀찮게하지 않을 것 같다 : D
표시 이름
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.