Một thách thức tối ưu hóa với các đồng tiền lạ


17

Bạn có những nđồng xu có trọng lượng -1 hoặc 1. Mỗi đồng tiền được dán nhãn từ 0để n-1bạn có thể phân biệt các đồng tiền. Bạn có một thiết bị cân (ma thuật) là tốt. Ở lượt đầu tiên, bạn có thể đặt bao nhiêu đồng xu tùy thích trên thiết bị cân có thể đo cả trọng lượng âm và dương và nó sẽ cho bạn biết chính xác chúng nặng bao nhiêu.

Tuy nhiên, có một cái gì đó thực sự kỳ lạ về thiết bị cân. Nếu bạn đặt tiền xu x_1, x_2, ..., x_jvào thiết bị lần đầu tiên, thì lần sau bạn phải đặt tiền (x_1+1), (x_2+1) , ..., (x_j+1)lên bàn cân với ngoại lệ là bạn tất nhiên không thể đặt đồng xu được đánh số cao hơn n-1. Không chỉ vậy, đối với mỗi lần cân mới bạn có thể chọn nếu bạn cũng muốn đặt coin 0lên bàn cân.

Theo quy tắc này, số cân nhỏ nhất sẽ luôn cho bạn biết chính xác đồng tiền nào nặng 1 và cân nào -1?

Rõ ràng bạn chỉ có thể đặt tiền xu 0vào thiết bị trong lượt đầu tiên và sau đó sẽ mất chính xác nđể giải quyết vấn đề.

Ngôn ngữ và thư viện

Bạn có thể sử dụng bất kỳ ngôn ngữ hoặc thư viện nào bạn thích (không được thiết kế cho thử thách này). Tuy nhiên, tôi muốn có thể kiểm tra mã của bạn nếu có thể vì vậy nếu bạn có thể cung cấp hướng dẫn rõ ràng về cách chạy mã trong Ubuntu sẽ được đánh giá rất cao.

Ghi bàn

Đối với một nsố điểm nhất định, điểm số của bạn được nchia cho số lượng cân bạn cần trong trường hợp xấu nhất. Điểm cao hơn do đó tốt hơn. Không có đầu vào cho câu đố này nhưng mục tiêu của bạn là tìm ra điểm nmà bạn có thể đạt điểm cao nhất.

Nếu có hòa, câu trả lời đầu tiên sẽ thắng. Trong tình huống cực kỳ khó xảy ra khi ai đó tìm được cách lấy điểm vô hạn, người đó sẽ thắng ngay lập tức.

Bài tập

Nhiệm vụ của bạn chỉ đơn giản là viết mã đạt điểm cao nhất. Mã của bạn sẽ phải chọn cả n một cách khéo léo và sau đó cũng tối ưu hóa số lượng cân cho điều đó n.

Mục hàng đầu

  • 4/3 7/5 bằng Python bởi SUND Borsch
  • 26/14 tại Java bởi Peter Taylor

8
Tôi rất muốn có được một số đồng tiền chống trọng lực.
mbomb007 04/11/2015

2
Tôi có một giải pháp không bao giờ sử dụng máy: Giữ từng đồng xu và xem cái nào kéo tay bạn lên, và cái nào kéo tay bạn xuống.
Vụ kiện của Quỹ Monica

1
Ngoài ra, như một lưu ý phụ, có thể tốt hơn là viết "nếu bạn cân các đồng xu từ a đến b, thì lần sau bạn phải thực hiện + 1 đến b + 1" (có thể với một 'ít nhất là' cũng được ném vào, và định dạng tốt hơn) thay vì đăng ký biểu thị số xu. Điều đó làm cho nó có vẻ như là một số tài sản hoặc số lượng xu _, thay vì chính đồng tiền đó.
Vụ kiện của Quỹ Monica

1
@ mbomb007 Ở mỗi lần cân, bạn có thể chọn cân 0 đồng cũng như tất cả các đồng tiền khác bạn sẽ cân. Nói cách khác, bạn có sự lựa chọn mới để thực hiện cho mỗi cân bạn làm.

3
@ mbomb007 @QPaysTaxes Về ký hiệu x_i: Ví dụ: chúng ta có thể có trọng lượng đầu tiên là (x_1, x_2, x_3) = (3, 2, 7), và sau đó cân thứ hai có thể là (4, 3, 8) hoặc ( 0, 4, 3, 8). Các nhãn coin không cần phải liên tiếp và chỉ mục itrong x_ikhông đề cập đến nhãn của coin.
Mitch Schwartz

Câu trả lời:


3

C ++, Điểm 23/12 25/13 27/14 28/14 = 2 31/15

Các giải pháp của thuộc tính Matrix X được xem xét lại (hoặc Joy of X) có thể sử dụng trực tiếp làm giải pháp cho vấn đề này. Ví dụ: giải pháp 31 hàng 15 cột:

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 

hàng N đại diện cho những đồng tiền bạn đặt lên thang đo lường cho dù kết quả của trọng số bạn nhận được là gì, rõ ràng có một số giá trị đồng xu mang lại trọng số đó. Nếu có một sự kết hợp khác nữa (giải pháp không phải là duy nhất) hãy xem xét chúng khác nhau như thế nào. Bạn phải thay thế một tập hợp các đồng xu có trọng số 1bằng các trọng số tiền xu -1. Điều này đưa ra một tập hợp các cột tương ứng với lật đó. Ngoài ra còn có bộ tiền xu -1mà bạn thay thế bằng 1. Đó là một tập hợp các cột khác. Bởi vì các phép đo không thay đổi giữa hai giải pháp có nghĩa là tổng cột của hai bộ phải giống nhau. Nhưng các giải pháp cho thuộc tính Matrix X được xem xét lại (hoặc Niềm vui của X) chính xác là các ma trận mà các bộ cột như vậy không tồn tại, do đó không có sự trùng lặp và mỗi giải pháp là duy nhất.

Mỗi bộ đo thực tế có thể được mô tả bằng một số 0/1ma trận. Nhưng ngay cả khi một số bộ cột tổng hợp với cùng một vectơ, đó có thể là dấu hiệu của các giá trị đồng xu của giải pháp ứng cử viên không tương ứng chính xác với một tập hợp như vậy. Vì vậy, tôi không biết liệu ma trận như trên có tối ưu không. Nhưng ít nhất họ cung cấp một giới hạn thấp hơn. Vì vậy, khả năng 31 đồng tiền có thể được thực hiện trong ít hơn 15 phép đo vẫn đang mở.

Lưu ý rằng điều này chỉ đúng với một chiến lược không cố định trong đó quyết định của bạn đặt đồng xu 0lên bàn cân phụ thuộc vào kết quả của các trọng số trước đó. Nếu không, bạn sẽ có các giải pháp trong đó các dấu hiệu của các đồng tiền tương ứng với các bộ có cùng tổng cột.


Kỷ lục thế giới hiện tại :)

Bạn ước tính cần một máy tính nhanh đến mức nào để lên 2?

@Lembik Tôi không tin 2 là có thể. Tôi không biết tại sao, nhưng kết quả hiện tại cho thấy bạn chỉ có thể tiếp cận gần 2 tùy ý mà không bao giờ tiếp cận được
TonMedel

Bạn có cơ hội để xác minh ma trận tuần hoàn 25 x 50 mà tôi đã dán sẽ cho 2 không? 01011011100010111101000001100111110011010100011010 là hàng đầu tiên của ma trận tuần hoàn.

Tôi không biết làm thế nào để kiểm tra ma trận đó mà không cần viết một chương trình chuyên dụng sẽ chạy trong một thời gian dài
TonMedel 9/11/2016

5

Python 2, điểm = 1.0

Đây là điểm số dễ dàng, trong trường hợp không ai tìm thấy điểm tốt hơn (nghi ngờ). ncân cho mỗi 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)

Tôi đã nhập antigravityđể chương trình có thể hoạt động với trọng số âm.


Rất hữu ích. Cảm ơn bạn :)

Nhập khẩu antigravityvề cơ bản là không có, phải không?
Sange Borsch

@SargeBorsch Với mục đích của chương trình này , nó là. Nhưng nó thực sự làm một cái gì đó.
mbomb007

5

Điểm = 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);
        }
    }
}

Lưu dưới dạng LembikWeighingOptimisation.java, biên dịch thành javac LembikWeighingOptimisation.java, chạy như java LembikWeighingOptimisation.

Rất cám ơn Mitch Schwartz đã chỉ ra một lỗi trong phiên bản đầu tiên của việc từ chối nhanh.

Điều này sử dụng một số kỹ thuật khá cơ bản mà tôi không thể biện minh một cách chặt chẽ. Nó mạnh mẽ, nhưng chỉ để bắt đầu các hoạt động cân sử dụng tối đa một nửa số tiền: các chuỗi sử dụng hơn một nửa số tiền không thể chuyển trực tiếp sang trọng lượng bổ sung (vì chúng tôi không biết tổng trọng lượng), nhưng ở mức độ lượn sóng bằng tay nên có cùng một lượng thông tin. Nó cũng lặp đi lặp lại thông qua việc bắt đầu cân theo số lượng tiền có liên quan, trên cơ sở cách nó bao gồm các trọng lượng phân tán (hy vọng cung cấp thông tin về đầu cuối tương đối sớm) mà không phải bò qua một bó bắt đầu bằng một tập hợp con dày đặc tại cuối cùng

Các MaskRangelớp học là một cải tiến lớn trên phiên bản trước đó về việc sử dụng bộ nhớ, và loại bỏ GC từ một nút cổ chai.

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  

Bạn có thể chắc chắn không nhận được 12/7? Tôi khá chắc chắn rằng hoạt động. Còn nữa, ngày 19/10 thì sao? Tôi nghĩ rằng mã của tôi đã cho tôi một lần nhưng tôi không thể sao chép nó ngay bây giờ.

@Lembik, tôi đã liệt kê 12/7, nhưng điều tốt nhất tôi có thể làm cho 19 là 19/11.
Peter Taylor

Ồ vâng xin lỗi. Có thể heuristic của bạn ném đi một số giải pháp? Tôi khá chắc chắn 19/10 cũng nên làm việc.

thể , vâng, nếu giải pháp duy nhất có trọng lượng ban đầu với hơn một nửa số tiền. Tôi hơi ngạc nhiên, mặc dù.
Peter Taylor

Có đáng để tăng một nửa ngưỡng lên hơn một nửa có thể chỉ để xem không?

2

Python 3, điểm = 4/3 = 1.33 Cầu (N = 4) điểm = 1,4 (N = 7)

Cập nhật: đã thực hiện tìm kiếm brute-force trong bộ giải "tĩnh" và có kết quả mới

Tôi nghĩ rằng nó có thể được cải thiện hơn nữa bằng cách tìm kiếm các bộ giải động, có thể sử dụng kết quả trọng số cho các quyết định tiếp theo.

Dưới đây là mã Python tìm kiếm thông qua tất cả các bộ giải tĩnh cho các n giá trị nhỏ (các bộ giải này luôn cân nhắc cùng một bộ đồng xu, do đó tên "tĩnh") và xác định số bước trong trường hợp xấu nhất của chúng bằng cách kiểm tra xem kết quả đo của chúng chỉ cho phép một đồng xu phù hợp thiết lập trong mọi trường hợp. Ngoài ra, nó theo dõi điểm số tốt nhất được tìm thấy cho đến nay và người giải mận sớm đã cho thấy rằng họ chắc chắn là tồi tệ hơn so với những người được tìm thấy trước đó. Đây là một tối ưu hóa quan trọng, nếu không tôi không thể chờ kết quả này với n= 7. (Nhưng rõ ràng nó vẫn chưa được tối ưu hóa tốt)

Hãy đặt câu hỏi nếu không rõ nó hoạt động như thế nào

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

Đầu ra:

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)

Dòng này mở (StaticSolver({0,2}, {0,1,3}, {0,1,2,4}, {1,2,3,5}, {0,2,3,4,6}), 5), score = 7/5 = 1.4ra bộ giải tốt nhất được tìm thấy. Các số trong {}dấu ngoặc nhọn là chỉ số của đồng xu để đặt trên thiết bị đo trọng lượng ở mỗi bước.


4
PS Tôi đã viết điều này trong khi nguồn điện trong nhà tôi bị hỏng, vì vậy tôi có một máy tính xách tay sử dụng pin và không có kết nối Internet, và tôi đơn giản không có việc gì để làm tốt hơn là phá vỡ một số câu đố. Tôi đoán tôi sẽ không bận tâm nếu tất cả đều ổn: D
SUND Borsch
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.