Bangun sepasang mata-mata yang akan melemparkan batu ke sungai

20

Baru-baru ini di Puzzling.SE yang baru dirilis , ada masalah tentang mata-mata melemparkan batu ke sungai yang sebenarnya cukup menantang:

Dua mata-mata harus saling melewati dua nomor rahasia (satu angka per mata-mata), tanpa disadari oleh musuh mereka. Mereka telah menyetujui metode untuk melakukan ini hanya dengan menggunakan 26 batu yang tidak bisa dibedakan sebelumnya.

Mereka bertemu di sungai, di mana ada tumpukan 26 batu. Dimulai dengan mata-mata pertama, mereka bergantian melemparkan sekelompok batu ke sungai: mata-mata pertama melempar sejumlah batu, lalu yang kedua, lalu yang pertama lagi ...

Setiap mata-mata harus melemparkan setidaknya satu batu pada gilirannya, sampai semua batu hilang.

Mereka mengamati semua lemparan dan berbeda ketika tidak ada lagi batu. Mereka diam sepanjang waktu dan tidak ada informasi yang dipertukarkan kecuali sejumlah batu yang dilemparkan pada setiap belokan.

Bagaimana mereka bisa menukar angka dengan sukses jika angka bisa dari 1 ke M?

Tugas Anda adalah membangun sepasang program, spy1dan spy2, yang dapat menyelesaikan masalah ini dengan setinggi mungkin M.

Setiap program Anda akan mengambil nomor dari yang 1Anda pilih Msebagai masukan. Kemudian, spy1akan menampilkan angka yang mewakili jumlah batu yang dilemparkannya ke sungai, yang akan menjadi input spy2yang juga akan menampilkan angka yang akan dimasukkan spy1, dan seterusnya hingga angka-angka yang dihasilkan bertambah 26. Pada akhir lemparan, setiap program akan menampilkan nomor yang diyakini oleh program lain, yang harus cocok dengan angka yang sebenarnya diinput ke program lain.

Program Anda harus bekerja untuk semua pasangan angka yang mungkin dipesan di (i, j)mana keduanya idan jdapat bervariasi dari 1hingga M.

Program yang bekerja untuk yang terbesar Makan menjadi pemenang, dengan jawaban pertama yang diposting melanggar dasi. Selain itu, saya akan memberikan hadiah +100 reputasi untuk solusi pertama yang terbukti berhasil M >= 2286, dan +300 untuk solusi pertama yang terbukti berhasil M >= 2535.

Joe Z.
sumber
Solusi berarti algoritme, atau program, yang menghasilkan serangkaian pembangkangan untuk masing-masing (i, j)?
klm123
Bukan satu program, tapi dua. Mereka harus berkomunikasi secara mandiri, seperti dalam masalah Anda.
Joe Z.
3
Karena program perlu membagikan pohon keputusan mereka, dapatkah kita menjadikannya satu program yang membutuhkan argumen untuk mengatakan mata-mata yang mana?
Peter Taylor
Selama Anda dapat menjamin bahwa masing-masing mata-mata berkomunikasi secara independen dan tidak ada informasi tambahan yang dipertukarkan di antara mereka.
Joe Z.
Secara independen saya telah memverifikasi bahwa 2535 adalah maks teori-informasi untuk masalah ini. Saya sangat percaya sekarang bahwa tidak ada program yang bisa lebih baik
nneonneo

Jawaban:

8

C #, M = 2535

Ini mengimplementasikan * sistem yang saya uraikan secara matematis pada utas yang memicu kontes ini. Saya mengklaim bonus 300 rep. Tes mandiri program jika Anda menjalankannya tanpa argumen baris perintah atau dengan --testsebagai argumen baris perintah; untuk mata-mata 1, jalankan dengan --spy1, dan untuk mata-mata 2 dengan --spy2. Dalam setiap kasus dibutuhkan nomor yang saya harus berkomunikasi dari stdin, dan kemudian melakukan lemparan melalui stdin dan stdout.

* Sebenarnya, saya telah menemukan optimasi yang membuat perbedaan besar (dari beberapa menit untuk menghasilkan pohon keputusan, hingga kurang dari satu detik); pohon yang dihasilkannya pada dasarnya sama, tetapi saya masih bekerja pada buktinya. Jika Anda menginginkan implementasi langsung dari sistem yang saya jelaskan di tempat lain, lihat revisi 2 , meskipun Anda mungkin ingin membuat cadangan dari logging tambahan Maindan lebih baik antar-thread dari TestSpyIO.

Jika Anda ingin test case yang selesai dalam waktu kurang dari satu menit, ubah Nke 16dan Mke 87.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace CodeGolf
{
    internal class Puzzle625
    {
        public static void Main(string[] args)
        {
            const int N = 26;
            const int M = 2535;

            var root = BuildDecisionTree(N);

            if (args.Length == 0 || args[0] == "--test")
            {
                DateTime startUtc = DateTime.UtcNow;
                Console.WriteLine("Built decision tree in {0}", DateTime.UtcNow - startUtc);
                startUtc = DateTime.UtcNow;

                int ok = 0;
                int fail = 0;
                for (int i = 1; i <= M; i++)
                {
                    for (int j = 1; j <= M; j++)
                    {
                        if (Test(i, j, root)) ok++;
                        else fail++;
                    }
                    double projectedTimeMillis = (DateTime.UtcNow - startUtc).TotalMilliseconds * M / i;
                    Console.WriteLine("Interim result: ok = {0}, fail = {1}, projected test time {2}", ok, fail, TimeSpan.FromMilliseconds(projectedTimeMillis));
                }
                Console.WriteLine("All tested: ok = {0}, fail = {1}, in {2}", ok, fail, DateTime.UtcNow - startUtc);
                Console.ReadKey();
            }
            else if (args[0] == "--spy1")
            {
                new Spy(new ConsoleIO(), root, true).Run();
            }
            else if (args[0] == "--spy2")
            {
                new Spy(new ConsoleIO(), root, false).Run();
            }
            else
            {
                Console.WriteLine("Usage: Puzzle625.exe [--test|--spy1|--spy2]");
            }
        }

        private static bool Test(int i, int j, Node root)
        {
            TestSpyIO io1 = new TestSpyIO("Spy 1");
            TestSpyIO io2 = new TestSpyIO("Spy 2");
            io1.Partner = io2;
            io2.Partner = io1;

            // HACK! Prime the input
            io2.Output(i);
            io1.Output(j);

            Spy spy1 = new Spy(io1, root, true);
            Spy spy2 = new Spy(io2, root, false);

            Thread th1 = new Thread(spy1.Run);
            Thread th2 = new Thread(spy2.Run);
            th1.Start();
            th2.Start();

            th1.Join();
            th2.Join();

            // Check buffer contents. Spy 2 should output spy 1's value, so it's lurking in io1, and vice versa.
            return io1.Input() == i && io2.Input() == j;
        }

        private static Node BuildDecisionTree(int numStones)
        {
            NodeValue[] trees = new NodeValue[] { NodeValue.Trivial };
            for (int k = 2; k <= numStones; k++)
            {
                int[] prev = trees.Select(nv => nv.Y).ToArray();
                List<int> row = new List<int>(prev);
                int cap = prev.Length;
                for (int i = 1; i <= prev[0]; i++)
                {
                    while (prev[cap - 1] < i) cap--;
                    row.Add(cap);
                }

                int[] next = row.OrderByDescending(x => x).ToArray();
                NodeValue[] nextTrees = new NodeValue[next.Length];
                nextTrees[0] = trees.Last().Reverse();
                for (int i = 1; i < next.Length; i++)
                {
                    int cp = next[i] - 1;
                    nextTrees[i] = trees[cp].Combine(trees[i - prev[cp]]);
                }

                trees = nextTrees;
            }

            NodeValue best = trees.MaxElement(v => Math.Min(v.X, v.Y));
            return BuildDecisionTree(numStones, best, new Dictionary<Pair<int, NodeValue>, Node>());
        }

        private static Node BuildDecisionTree(int numStones, NodeValue val, IDictionary<Pair<int, NodeValue>, Node> cache)
        {
            // Base cases
            // NB We might get passed val null with 0 stones, so we hack around that
            if (numStones == 0) return new Node(NodeValue.Trivial, new Node[0]);

            // Cache
            Pair<int, NodeValue> key = new Pair<int, NodeValue>(numStones, val);
            Node node;
            if (cache.TryGetValue(key, out node)) return node;

            // The pair-of-nodes construction is based on a bijection between
            //     $\prod_{i<k} T_i \cup \{(\infty, 0)\}$
            // and
            //     $(T_{k-1} \cup \{(\infty, 0)\}) \times \prod_{i<k-1} T_i \cup \{(\infty, 0)\}$

            // val.Left represents the element of $T_{k-1} \cup \{(\infty, 0)\}$ (using null for the $(\infty, 0)$)
            // and val.Right represents $\prod_{i<k-1} T_i \cup \{(\infty, 0)\}$ by bijection with $T_{k-1} \cup \{(\infty, 0)\}$.
            // so val.Right.Left represents the element of $T_{k-2}$ and so on.
            // The element of $T_{k-i}$ corresponds to throwing $i$ stones.
            Node[] children = new Node[numStones];
            NodeValue current = val;
            for (int i = 0; i < numStones && current != null; i++)
            {
                children[i] = BuildDecisionTree(numStones - (i + 1), current.Left, cache);
                current = current.Right;
            }
            node = new Node(val, children);

            // Cache
            cache[key] = node;
            return node;
        }

        class Pair<TFirst, TSecond>
        {
            public readonly TFirst X;
            public readonly TSecond Y;

            public Pair(TFirst x, TSecond y)
            {
                this.X = x;
                this.Y = y;
            }

            public override string ToString()
            {
                return string.Format("({0}, {1})", X, Y);
            }

            public override bool Equals(object obj)
            {
                Pair<TFirst, TSecond> other = obj as Pair<TFirst, TSecond>;
                return other != null && object.Equals(other.X, this.X) && object.Equals(other.Y, this.Y);
            }

            public override int GetHashCode()
            {
                return X.GetHashCode() + 37 * Y.GetHashCode();
            }
        }

        class NodeValue : Pair<int, int>
        {
            public readonly NodeValue Left;
            public readonly NodeValue Right;

            public static NodeValue Trivial = new NodeValue(1, 1, null, null);

            private NodeValue(int x, int y, NodeValue left, NodeValue right) : base(x, y)
            {
                this.Left = left;
                this.Right = right;
            }

            public NodeValue Reverse()
            {
                return new NodeValue(Y, X, this, null);
            }

            public NodeValue Combine(NodeValue other)
            {
                return new NodeValue(other.X + Y, Math.Min(other.Y, X), this, other);
            }
        }

        class Node
        {
            public readonly NodeValue Value;
            private readonly Node[] _Children;

            public Node this[int n]
            {
                get { return _Children[n]; }
            }

            public int RemainingStones
            {
                get { return _Children.Length; }
            }

            public Node(NodeValue value, IEnumerable<Node> children)
            {
                this.Value = value;
                this._Children = children.ToArray();
            }
        }

        interface SpyIO
        {
            int Input();
            void Output(int i);
        }

        // TODO The inter-thread communication here can almost certainly be much better
        class TestSpyIO : SpyIO
        {
            private object _Lock = new object();
            private int? _Buffer;
            public TestSpyIO Partner;
            public readonly string Name;

            internal TestSpyIO(string name)
            {
                this.Name = name;
            }

            public int Input()
            {
                lock (_Lock)
                {
                    while (!_Buffer.HasValue) Monitor.Wait(_Lock);

                    int rv = _Buffer.Value;
                    _Buffer = null;
                    Monitor.PulseAll(_Lock);
                    return rv;
                }
            }

            public void Output(int i)
            {
                lock (Partner._Lock)
                {
                    while (Partner._Buffer.HasValue) Monitor.Wait(Partner._Lock);
                    Partner._Buffer = i;
                    Monitor.PulseAll(Partner._Lock);
                }
            }
        }

        class ConsoleIO : SpyIO
        {
            public int Input()
            {
                return Convert.ToInt32(Console.ReadLine());
            }

            public void Output(int i)
            {
                Console.WriteLine("{0}", i);
            }
        }

        class Spy
        {
            private readonly SpyIO _IO;
            private Node _Node;
            private bool _MyTurn;

            internal Spy(SpyIO io, Node root, bool isSpy1)
            {
                this._IO = io;
                this._Node = root;
                this._MyTurn = isSpy1;
            }

            internal void Run()
            {
                int myValue = _IO.Input() - 1;
                int hisValue = 1;

                bool myTurn = _MyTurn;
                Node n = _Node;
                while (n.RemainingStones > 0)
                {
                    if (myTurn)
                    {
                        if (myValue >= n.Value.X) throw new Exception("Internal error");
                        for (int i = 0; i < n.RemainingStones; i++)
                        {
                            // n[i] allows me to represent n[i].Y values: 0 to n[i].Y - 1
                            if (myValue < n[i].Value.Y)
                            {
                                _IO.Output(i + 1);
                                n = n[i];
                                break;
                            }
                            else myValue -= n[i].Value.Y;
                        }
                    }
                    else
                    {
                        int thrown = _IO.Input();
                        for (int i = 0; i < thrown - 1; i++)
                        {
                            hisValue += n[i].Value.Y;
                        }
                        n = n[thrown - 1];
                    }

                    myTurn = !myTurn;
                }

                _IO.Output(hisValue);
            }
        }
    }

    static class LinqExt
    {
        // I'm not sure why this isn't built into Linq.
        public static TElement MaxElement<TElement>(this IEnumerable<TElement> e, Func<TElement, int> f)
        {
            int bestValue = int.MinValue;
            TElement best = default(TElement);
            foreach (var elt in e)
            {
                int value = f(elt);
                if (value > bestValue)
                {
                    bestValue = value;
                    best = elt;
                }
            }
            return best;
        }
    }
}

Instruksi untuk pengguna Linux

Anda harus mono-cscmengkompilasi (pada sistem berbasis Debian, ada dalam mono-develpaket) dan monomenjalankan ( mono-runtimepaket). Maka mantra adalah

mono-csc -out:codegolf31673.exe codegolf31673.cs
mono codegolf31673.exe --test

dll.

Peter Taylor
sumber
2
Apakah itu C #? Saya tidak tahu bagaimana menjalankannya di Linux.
Joe Z.
Selama ini saya pikir saya melakukan sesuatu yang salah. Ternyata, membangun pohon keputusan hanya membutuhkan waktu 30 menit ... Sebagai catatan, ini bekerja pada Fedora 20: 1. yum install mono-core(sebagai root). 2. dmcs Puzzle625.cs3.mono Puzzle625.exe --test
Dennis
@ Dennis, saya berpikir bahwa JIT Mono tidak sebagus Microsoft. Saya punya beberapa ide untuk optimasi, tetapi saya belum selesai mengujinya.
Peter Taylor
Repositori Fedora menyediakan versi 2.10.8, yang berusia lebih dari dua tahun. Mungkin versi yang lebih baru lebih cepat. Saya ingin tahu: Berapa lama waktu yang dibutuhkan dengan Microsoft?
Dennis
2
Dari 30 menit hingga 39 mikrodetik. Itulah yang saya sebut optimasi!
Dennis
1

Program Penguji Python

Saya pikir akan bermanfaat untuk memiliki program pengujian yang dapat memverifikasi bahwa implementasi Anda berfungsi. Kedua skrip di bawah ini berfungsi dengan Python 2 atau Python 3.

Program penguji ( tester.py):

import sys
import shlex
from subprocess import Popen, PIPE

def writen(p, n):
    p.stdin.write(str(n)+'\n')
    p.stdin.flush()

def readn(p):
    return int(p.stdout.readline().strip())

MAXSTONES = 26

def test_one(spy1cmd, spy2cmd, n1, n2):
    p1 = Popen(spy1cmd, stdout=PIPE, stdin=PIPE, universal_newlines=True)
    p2 = Popen(spy2cmd, stdout=PIPE, stdin=PIPE, universal_newlines=True)

    nstones = MAXSTONES

    writen(p1, n1)
    writen(p2, n2)

    p1turn = True
    while nstones > 0:
        if p1turn:
            s = readn(p1)
            writen(p2, s)
        else:
            s = readn(p2)
            writen(p1, s)
        if s <= 0 or s > nstones:
            print("Spy %d output an illegal number of stones: %d" % ([2,1][p1turn], s))
            return False
        p1turn = not p1turn
        nstones -= s

    n1guess = readn(p2)
    n2guess = readn(p1)

    if n1guess != n1:
        print("Spy 2 output wrong answer: expected %d, got %d" % (n1, n1guess))
        return False
    elif n2guess != n2:
        print("Spy 1 output wrong answer: expected %d, got %d" % (n2, n2guess))
        return False

    p1.kill()
    p2.kill()

    return True

def testrand(spy1, spy2, M):
    import random
    spy1cmd = shlex.split(spy1)
    spy2cmd = shlex.split(spy2)

    n = 0
    while 1:
        i = random.randrange(1, M+1)
        j = random.randrange(1, M+1)
        test_one(spy1cmd, spy2cmd, i, j)
        n += 1
        if n % 100 == 0:
            print("Ran %d tests" % n)

def test(spy1, spy2, M):
    spy1cmd = shlex.split(spy1)
    spy2cmd = shlex.split(spy2)
    for i in range(1, M+1):
        print("Testing %d..." % i)
        for j in range(1, M+1):
            if not test_one(spy1cmd, spy2cmd, i, j):
                print("Spies failed the test.")
                return
    print("Spies passed the test.")

if __name__ == '__main__':
    if len(sys.argv) != 4:
        print("Usage: %s <M> <spy1> <spy2>: test programs <spy1> and <spy2> with limit M" % sys.argv[0])
        exit()

    M = int(sys.argv[1])
    test(sys.argv[2], sys.argv[3], M)

Protokol: Dua program mata-mata yang ditentukan pada command-line akan dieksekusi. Mereka diharapkan berinteraksi hanya melalui stdin / stdout. Setiap program akan menerima nomor yang ditugaskan sebagai input pertama. Di setiap belokan, mata-mata 1 menghasilkan jumlah batu yang akan dilempar, mata-mata 2 membaca angka dari stdin (mewakili lemparan mata-mata 1), dan kemudian mereka mengulangi (dengan posisi terbalik). Ketika salah satu mata-mata menentukan bahwa 26 batu telah dilempar, mereka berhenti dan mengeluarkan tebakan mereka untuk nomor mata-mata lainnya.

Sesi contoh dengan spy1 yang kompatibel ( >menunjukkan input ke spy)

> 42
7
> 5
6
> 3
5
27
<program quits>

Jika Anda memilih M yang sangat besar, dan waktu terlalu lama untuk menjalankan, Anda dapat beralih test(untuk testrand(di baris terakhir untuk menjalankan tes acak. Dalam kasus terakhir, biarkan program berjalan setidaknya untuk beberapa ribu percobaan untuk membangun kepercayaan.

Contoh program ( spy.py), untuk M = 42:

import sys

# Carry out the simple strategy for M=42

def writen(n):
    sys.stdout.write(str(n)+"\n")
    sys.stdout.flush()

def readn():
    return int(sys.stdin.readline().strip())

def spy1(n):
    m1,m2 = divmod(n-1, 6)
    writen(m1+1)
    o1 = readn() # read spy2's number

    writen(m2+1)
    o2 = readn()

    rest = 26 - (m1+m2+o1+o2+2)
    if rest > 0:
        writen(rest)
    writen((o1-1)*6 + (o2-1) + 1)

def spy2(n):
    m1,m2 = divmod(n-1, 6)
    o1 = readn() # read spy1's number
    writen(m1+1)

    o2 = readn()
    writen(m2+1)

    rest = 26 - (m1+m2+o1+o2+2)
    if rest > 0:
        readn()

    writen((o1-1)*6 + (o2-1) + 1)

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: %s [spy1|spy2]" % (sys.argv[0]))
        exit()

    n = int(input())
    if sys.argv[1] == 'spy1':
        spy1(n)
    elif sys.argv[1] == 'spy2':
        spy2(n)
    else:
        raise Exception("Must give spy1 or spy2 as an argument.")

Contoh penggunaan:

python tester.py 42 'python spy.py spy1' 'python spy.py spy2'
nneonneo
sumber
1

Java, M = 2535

OKE, ini implementasi saya. Pada setiap langkah satu mata-mata bergerak. Setiap langkah yang mungkin mewakili berbagai kode. Mata-mata memilih langkah yang cocok dengan kode rahasianya. Saat mereka melempar lebih banyak batu, kisaran kode yang mungkin berkurang hingga, pada akhirnya, untuk kedua mata-mata, hanya satu kode yang tetap mungkin sesuai dengan gerakan yang mereka lakukan.

Untuk memulihkan kode rahasia, Anda dapat memutar ulang semua gerakan dan menghitung rentang kode yang sesuai. Pada akhirnya, hanya satu kode yang tersisa untuk setiap mata-mata, yaitu kode rahasia yang ingin ia kirimkan.

Sayangnya, algoritma ini mengandalkan tabel prekomputasi besar dengan ratusan ribu bilangan bulat. Metode ini tidak dapat diterapkan secara mental dengan lebih dari 8-10 batu.

File pertama mengimplementasikan algoritma Spy. Bagian statis precomputes codeCounttabel yang kemudian digunakan untuk menghitung setiap gerakan. Bagian kedua menerapkan 2 prosedur, satu untuk memilih berapa banyak batu untuk dilempar, yang lain untuk memutar ulang langkah untuk membantu merekonstruksi kode rahasia.

File kedua menguji kelas Spy secara ekstensif. Metode ini simulatemensimulasikan proses. Menggunakan kelas Spy untuk menghasilkan urutan lemparan dari kode rahasia dan kemudian merekonstruksi kode dari urutan.

Spy.java

package stackexchange;

import java.util.Arrays;

public class Spy
{
    // STATIC MEMBERS

    /** Size of code range for a number of stones left to the other and the other spy's range */
    static int[][] codeCount;

    // STATIC METHODS

    /** Transpose an array of code counts */
    public static int[] transpose(int[] counts){
        int[] transposed = new int[counts[1]+1];
        int s = 0;
        for( int i=counts.length ; i-->0 ; ){
            while( s<counts[i] ){
                transposed[++s] = i;
            }
        }
        return transposed;
    }

    /** Add two integer arrays by element.  Assume the first is longer. */
    public static int[] add(int[] a, int[] b){
        int[] sum = a.clone();
        for( int i=0 ; i<b.length ; i++ ){
            sum[i] += b[i];
        }
        return sum;
    }

    /** Compute the code range for every response */
    public static void initCodeCounts(int maxStones){
        codeCount = new int[maxStones+1][];
        codeCount[0] = new int[] {0,1};
        int[] sum = codeCount[0];
        for( int stones=1 ; stones<=maxStones ; stones++ ){
            codeCount[stones] = transpose(sum);
            sum = add(codeCount[stones], sum);
        }
    }

    /** display the code counts */
    public static void dispCodeCounts(int maxStones){
        for( int stones=1 ; stones<=maxStones ; stones++ ){
            if( stones<=8 ){
                System.out.println(stones + ": " + Arrays.toString(codeCount[stones]));
            }
        }
        for( int s=1 ; s<=maxStones ; s++ ){
            int[] row = codeCount[s];
            int best = 0;
            for( int r=1 ; r<row.length ; r++ ){
                int min = r<row[r] ? r : row[r];
                if( min>=best ){
                    best = min;
                }
            }
            System.out.println(s + ": " + row.length + " " + best);
        }
    }

    /** Find the maximum symmetrical code count M for a number of stones */
    public static int getMaxValue(int stones){
        int[] row = codeCount[stones];
        int maxValue = 0;
        for( int r=1 ; r<row.length ; r++ ){
            int min = r<row[r] ? r : row[r];
            if( min>=maxValue ){
                maxValue = min;
            }
        }
        return maxValue;
    }

    // MEMBERS

    /** low end of range, smallest code still possible */
    int min;

    /** range size, number of codes still possible */
    int range;

    /** Create a spy for a certain number of stones */
    Spy(int stones){
        min = 1;
        range = getMaxValue(stones);
    }

    /** Choose how many stones to throw */
    public int throwStones(int stonesLeft, int otherRange, int secret){
        for( int move=1 ; ; move++ ){
            // see how many codes this move covers
            int moveRange = codeCount[stonesLeft-move][otherRange];
            if( secret < this.min+moveRange ){
                // secret code is in move range
                this.range = moveRange;
                return move;
            }
            // skip to next move
            this.min += moveRange;
            this.range -= moveRange;
        }
    }

    /* Replay the state changes for a given move */
    public void replayThrow(int stonesLeft, int otherRange, int stonesThrown){
        for( int move=1 ; move<stonesThrown ; move++ ){
            int moveRange = codeCount[stonesLeft-move][otherRange];
            this.min += moveRange;
            this.range -= moveRange;
        }
        this.range = codeCount[stonesLeft-stonesThrown][otherRange];
    }
}

ThrowingStones.java

package stackexchange;

public class ThrowingStones
{
    public boolean simulation(int stones, int secret0, int secret1){

        // ENCODING

        Spy spy0 = new Spy(stones);
        Spy spy1 = new Spy(stones);

        int[] throwSequence = new int[stones+1];
        int turn = 0;
        int stonesLeft = stones;

        while( true ){
            // spy 0 throws
            if( stonesLeft==0 ) break;
            throwSequence[turn] = spy0.throwStones(stonesLeft, spy1.range, secret0);
            stonesLeft -= throwSequence[turn++];
            // spy 1 throws
            if( stonesLeft==0 ) break;
            throwSequence[turn] = spy1.throwStones(stonesLeft, spy0.range, secret1);
            stonesLeft -= throwSequence[turn++];
        }

        assert (spy0.min==secret0 && spy0.range==1 );
        assert (spy1.min==secret1 && spy1.range==1 );

//      System.out.println(Arrays.toString(throwSequence));

        // DECODING

        spy0 = new Spy(stones);
        spy1 = new Spy(stones);

        stonesLeft = stones;
        turn = 0;
        while( true ){
            // spy 0 throws
            if( throwSequence[turn]==0 ) break;
            spy0.replayThrow(stonesLeft, spy1.range, throwSequence[turn]);
            stonesLeft -= throwSequence[turn++];
            // spy 1 throws
            if( throwSequence[turn]==0 ) break;
            spy1.replayThrow(stonesLeft, spy0.range, throwSequence[turn]);
            stonesLeft -= throwSequence[turn++];
        }
        int recovered0 = spy0.min;
        int recovered1 = spy1.min;

        // check the result
        if( recovered0 != secret0 || recovered1 != secret1 ){
            System.out.println("error recovering (" + secret0 + "," + secret1 + ")"
                    + ", returns (" + recovered0 + "," + recovered1 + ")");
            return false;
        }
        return true;
    }

    /** verify all possible values */
    public void verifyAll(int stones){
        int count = 0;
        int countOK = 0;
        int maxValue = Spy.getMaxValue(stones);
        for( int a=1 ; a<=maxValue ; a++ ){
            for( int b=1 ; b<=maxValue ; b++ ){
                count++;
                if( simulation(stones, a, b) ) countOK++;
            }
        }
        System.out.println("verified: " + countOK + "/" + count);
    }

    public static void main(String[] args) {
        ThrowingStones app = new ThrowingStones();
        Spy.initCodeCounts(26);
        Spy.dispCodeCounts(26);
        app.verifyAll(20);
//      app.verifyAll(26); // never managed to complete this one...
    }

}

Untuk referensi, array codeCount yang dikomputasi mengandung nilai-nilai berikut:

1: [0, 1]
2: [0, 1, 1]
3: [0, 2, 1, 1]
4: [0, 3, 2, 1, 1, 1]
5: [0, 5, 3, 2, 2, 1, 1, 1, 1]
6: [0, 8, 5, 4, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1]

Ini terkait langsung dengan perangkat Peter Taylor Tk. Kita punya:

(x,y) in Tk  <=>  y <= codeCount[x]
Florian F
sumber
Saya tidak berpikir ini cukup memenuhi spesifikasi tanpa cara untuk menjalankan kedua mata-mata dalam proses terpisah dan mengkomunikasikan lemparan tanpa berbagi akses ke rangebidang mereka . Tapi saya sangat tertarik dengan metode Anda menghitung tabel. Apakah Anda memiliki bukti kebenaran? Dan apakah Anda tertarik berkolaborasi dalam makalah yang membahas masalah dan menghitung solusinya?
Peter Taylor
Rentang mata-mata lainnya adalah fungsi dari gerakan masa lalu, karena dihitung dalam metode "replay". Saya percaya itu benar. Tabel yang saya hitung persis sama dengan yang Anda tetapkan Tk. Transposing pertukaran tabel x dan y, jumlah adalah jumlah semua anak yang mungkin dari suatu simpul. Saya belum membuktikannya dengan benar, kecuali bahwa saya telah mengujinya hingga 22 batu. Saya mencoba menulis jawaban yang tepat untuk membingungkan.stackexchange, tetapi saya belum berhasil menjelaskannya dengan jelas dan meyakinkan. Dan sebagian besar, itu sudah Anda lakukan.
Florian F
Baik. Saya mungkin tidak punya waktu minggu ini, tetapi ketika saya kurang sibuk saya akan mencoba untuk menemukan bukti bahwa metode generasi Anda menghasilkan tabel yang sama dengan saya, karena saya pikir itu akan menjadi tambahan yang baik untuk hal-hal yang saya ' sudah menulis.
Peter Taylor
Sebenarnya itu cukup sederhana: kesetaraannya dengan metode kalkulasi saya sampai pada lemma bahwa konjugat multiset-union dari dua partisi sama dengan jumlah titik konjugat konjugat mereka.
Peter Taylor
(menampar kepalaku) Tapi tentu saja! Kenapa aku tidak memikirkan itu sebelumnya? :-)
Florian F
0

ksh / zsh, M = 126

Dalam sistem sederhana ini, setiap mata-mata melempar angka biner ke mata-mata lainnya. Dalam setiap lemparan, batu pertama diabaikan, batu berikutnya masing-masing bit 0, dan batu terakhir adalah bit 1. Misalnya, untuk melempar 20, seorang mata-mata akan melempar 4 batu (abaikan, 0, 2, tambahkan 4), lalu lempar 3 batu (abaikan, 8, tambah 16), karena 4 + 16 = 20.

Himpunan angka tidak berdekatan. 0 hingga 126 ada, tetapi 127 keluar. (Jika kedua mata-mata memiliki 127, mereka membutuhkan 28 batu, tetapi mereka memiliki 26 batu.) Kemudian 128 hingga 158 berada, 159 keluar, 160 hingga 174 masuk, 175 keluar, 176 hingga 182 masuk, 183 keluar, 184 hingga 186 ada di dalam, 187 ada di luar, dan seterusnya.

Jalankan swap otomatis dengan ksh spy.sh 125 126, atau jalankan mata-mata individual dengan ksh spy.sh spy1 125dan ksh spy.sh spy2 126. Di sini, kshbisa ksh93, pdksh atau zsh.

EDIT 14 Jun 2014: Perbaiki masalah dengan beberapa proses bersama di zsh. Mereka akan menganggur selamanya dan gagal keluar, sampai pengguna membunuh mereka.

(( stones = 26 ))

# Initialize each spy.
spy_init() {
    (( wnum = $1 ))  # my number
    (( rnum = 0 ))   # number from other spy
    (( rlog = -1 ))  # exponent from other spy
}

# Read stone count from other spy.
spy_read() {
    read count || exit
    (( stones -= count ))

    # Ignore 1 stone.
    (( count > 1 )) && {
        # Increment exponent.  Add bit to number.
        (( rlog += count - 1 ))
        (( rnum += 1 << rlog ))
    }
}

# Write stone count to other spy.
spy_write() {
    if (( wnum ))
    then
        # Find next set bit.  Prepare at least 2 stones.
        (( count = 2 ))
        until (( wnum & 1 ))
        do
            (( wnum >>= 1 ))
            (( count += 1 ))
        done

        (( wnum >>= 1 ))  # Remove this bit.
        (( stones -= count ))
        print $count      # Throw stones.
    else
        # Throw 1 stone for other spy to ignore.
        (( stones -= 1 ))
        print 1
    fi
}

# spy1 writes first.
spy1() {
    spy_init "$1"
    while (( stones ))
    do
        spy_write
        (( stones )) || break
        spy_read
    done
    print $rnum
}

# spy2 reads first.
spy2() {
    spy_init "$1"
    while (( stones ))
    do
        spy_read
        (( stones )) || break
        spy_write
    done
    print $rnum
}

(( $# == 2 )) || {
    name=${0##*/}
    print -u2 "usage: $name number1 number2"
    print -u2 "   or: $name spy[12] number"
    exit 1
}

case "$1" in
    spy1)
        spy1 "$2"
        exit;;
    spy2)
        spy2 "$2"
        exit;;
esac

(( number1 = $1 ))
(( number2 = $2 ))

if [[ -n $KSH_VERSION ]]
then
    eval 'cofork() { "$@" |& }'
elif [[ -n $ZSH_VERSION ]]
then
    # In zsh, a co-process stupidly inherits its own >&p, so it never
    # reads end of file.  Use 'coproc :' to close <&p and >&p.
    eval 'cofork() {
        coproc {
            coproc :
            "$@"
        }
    }'
fi

# Fork spies in co-processes.
[[ -n $KSH_VERSION ]] && eval 'coproc() { "$@" |& }'
cofork spy1 number1
exec 3<&p 4>&p
cofork spy2 number2
exec 5<&p 6>&p

check_stones() {
    (( stones -= count ))
    if (( stones < 0 ))
    then
        print -u2 "$1 is in trouble! " \
            "Needs $count stones, only had $((stones + count))."
        exit 1
    else
        print "$1 threw $count stones.  Pile has $stones stones."
    fi
}

# Relay stone counts while spies throw stones.
while (( stones ))
do
    # First, spy1 writes to spy2.
    read -u3 count report1 || mia spy1
    check_stones spy1
    print -u6 $count

    (( stones )) || break

    # Next, spy2 writes to spy1.
    read -u5 count report2 || mia spy2
    check_stones spy2
    print -u4 $count
done

mia() {
    print -u2 "$1 is missing in action!"
    exit 1
}

# Read numbers from spies.
read -u3 report1 || mia spy1
read -u5 report2 || mia spy2

pass=true
(( number1 != report2 )) && {
    print -u2 "FAILURE: spy1 put $number1, but spy2 got $report2."
    pass=false
}
(( number2 != report1 )) && {
    print -u2 "FAILURE: spy2 put $number2, but spy1 got $report1."
    pass=false
}

if $pass
then
    print "SUCCESS: spy1 got $report1, spy2 got $report2."
    exit 0
else
    exit 1
fi
kernigh
sumber