奇异硬币带来的优化挑战


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
  • Java / 26/14的作者Peter Taylor

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)。硬币标签不必是连续的,并且iin 的索引x_i不指向硬币的标签。
米奇·施瓦兹

Answers:


3

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

重新讨论Matrix属性X的解决方案(或X的Joy)可以直接用作此问题的解决方案。例如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。那是另一组列。由于在两个解决方案之间测量值不变,这意味着两组的列总和必须相同。但是重新探讨Matrix属性X的解决方案(或X的喜悦) 正是这些矩阵不存在的这些矩阵,因此没有重复项,并且每个解决方案都是唯一的。

每个实际的测量值集可以由某个0/1矩阵描述。但是,即使某些列集合之和等于相同的向量,也可能是候选解决方案硬币值的符号与该集合不完全对应。因此,我不知道上述矩阵是否最优。但是至少它们提供了一个下限。因此,用不到15次测量就可以完成31个硬币的可能性仍然存在。

请注意,这仅适用于非固定策略,0在该策略中,您将硬币放到秤上的决定取决于先前加权的结果。否则,您获得解决方案,其中硬币的符号与列总和相同的集合相对应。


当前的世界纪录:)

您估计要达到2台计算机需要多快?

@Lembik我不相信2是可能的。我不知道为什么,但是目前的结果表明,您只能任意接近2个,而永远无法达到
Ton Hospel

您是否有机会验证我粘贴的25 x 50循环矩阵,该矩阵应为2?01011011100010111111000001100111110011010100011010作为循环矩阵的第一行。

我不知道如何在不编写将运行很长时间的专用程序的情况下检查该矩阵
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)

更新:在“静态”求解器集中实施了蛮力搜索,并获得了新结果

我认为可以通过搜索动态求解器来进一步改进它,可以将加权结果用于进一步的决策。

这是一条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
附言:我是在家里的电源坏了的时候写这篇文章的,所以我有一台笔记本电脑依靠电池供电,没有互联网连接,除了解决一些难题之外,我没有更好的事情要做。我想如果一切都好,我就不会打扰:D
显示名称
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.