Xây dựng một cặp điệp viên sẽ ném đá xuống sông


20

Gần đây tại Puzzling.SE mới được phát hành , có một vấn đề về các điệp viên ném đá xuống một con sông thực sự khá khó khăn:

Hai điệp viên phải truyền cho nhau hai số bí mật (một số cho mỗi điệp viên), không bị kẻ thù của họ chú ý. Họ đã đồng ý về một phương pháp để làm điều này chỉ sử dụng 26 viên đá không thể phân biệt trước.

Họ gặp nhau tại một con sông, nơi có một đống 26 viên đá. Bắt đầu với điệp viên đầu tiên, họ thay phiên nhau ném một nhóm đá xuống sông: điệp viên đầu tiên ném một số viên đá, rồi viên thứ hai, rồi viên thứ nhất lại ...

Mỗi gián điệp phải ném ít nhất một viên đá vào lượt của mình, cho đến khi tất cả các viên đá biến mất.

Họ quan sát tất cả các cú ném và phân kỳ khi không còn đá nữa. Họ giữ im lặng mọi lúc và không có thông tin nào được trao đổi ngoại trừ số lượng đá ném ở mỗi lượt.

Làm thế nào họ có thể trao đổi số thành công nếu các số có thể từ 1 đến M?

Nhiệm vụ của bạn là xây dựng một cặp chương trình, spy1spy2, điều đó có thể giải quyết vấn đề này ở mức cao nhất có thể M.

Mỗi chương trình của bạn sẽ lấy một số từ 1bạn chọn Mlàm đầu vào. Sau đó, spy1sẽ xuất ra một số đại diện cho số lượng đá mà nó ném xuống sông, nó sẽ là đầu vào spy2cũng sẽ xuất ra một số được nhập vào spy1, và cứ thế cho đến khi đầu ra số cộng vào 26. Khi kết thúc trò chơi, mỗi chương trình sẽ xuất ra số mà nó tin rằng chương trình kia có, phải khớp với số thực sự được nhập vào chương trình khác.

Chương trình của bạn phải làm việc cho tất cả các cặp có thứ tự có thể có của số (i, j)nơi cả hai ijcó thể thay đổi từ 1tới M.

Chương trình hoạt động cho lớn nhất Msẽ là người chiến thắng, với câu trả lời đầu tiên được đăng lên phá vỡ một chiếc cà vạt. Ngoài ra, tôi sẽ trao phần thưởng danh tiếng +100 cho giải pháp đầu tiên được chứng minh là có hiệu quả M >= 2286và +300 cho giải pháp đầu tiên được chứng minh là có hiệu quả M >= 2535.


Giải pháp có nghĩa là thuật toán, hoặc một chương trình, tạo ra tập hợp các phân tích cho mỗi (i, j)?
klm123

Không phải một chương trình, mà là hai. Họ phải giao tiếp độc lập, như trong vấn đề của bạn.
Joe Z.

3
Vì các chương trình sẽ cần chia sẻ cây quyết định của chúng, chúng ta có thể biến nó thành một chương trình cần tranh luận để nói đó là gián điệp nào không?
Peter Taylor

Miễn là bạn có thể đảm bảo rằng mỗi điệp viên giao tiếp độc lập và không có thêm thông tin trao đổi giữa họ.
Joe Z.

Độc lập tôi đã xác minh rằng 2535 là tối đa lý thuyết thông tin cho vấn đề này. Tôi khá tin tưởng rằng bây giờ không có chương trình nào có thể làm tốt hơn.
nneonneo

Câu trả lời:


8

C #, M = 2535

Điều này thực hiện * hệ thống mà tôi đã mô tả một cách toán học trên chuỗi đã kích thích cuộc thi này. Tôi yêu cầu tiền thưởng 300 rep. Chương trình tự kiểm tra nếu bạn chạy nó mà không có đối số dòng lệnh hoặc với --testtư cách là đối số dòng lệnh; cho gián điệp 1, chạy với --spy1và cho gián điệp 2 với --spy2. Trong mỗi trường hợp, nó sẽ lấy số mà tôi nên giao tiếp từ stdin, và sau đó thực hiện các cú ném thông qua stdin và stdout.

* Trên thực tế, tôi đã tìm thấy một tối ưu hóa tạo ra sự khác biệt lớn (từ vài phút để tạo cây quyết định, chưa đến một giây); cây mà nó tạo ra về cơ bản là giống nhau, nhưng tôi vẫn đang nghiên cứu một bằng chứng về điều đó. Nếu bạn muốn triển khai trực tiếp hệ thống mà tôi đã mô tả ở nơi khác, hãy xem phiên bản 2 , mặc dù bạn có thể muốn nhập nhật ký bổ sung từ Mainvà giao tiếp liên kết tốt hơn từ đó TestSpyIO.

Nếu bạn muốn một trường hợp thử nghiệm hoàn thành trong vòng chưa đầy một phút, hãy đổi Nthành 16Mthành 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;
        }
    }
}

Hướng dẫn cho người dùng Linux

Bạn sẽ cần mono-cscphải biên dịch (trên các hệ thống dựa trên Debian, nó có trong mono-develgói) và monođể chạy ( mono-runtimegói). Sau đó, câu thần chú là

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

v.v.


2
Có phải đó là C #? Tôi không biết làm thế nào để chạy nó trên Linux.
Joe Z.

Tất cả thời gian này tôi nghĩ rằng tôi đã làm điều gì đó sai. Hóa ra, việc xây dựng cây quyết định chỉ mất 30 phút ... Đối với hồ sơ, việc này hoạt động trên Fedora 20: 1. yum install mono-core(với quyền root). 2. dmcs Puzzle625.cs3.mono Puzzle625.exe --test
Dennis

@Dennis, tôi nghĩ rằng JIT của Mono không tốt bằng Microsoft. Tôi có một số ý tưởng để tối ưu hóa, nhưng tôi chưa hoàn thành thử nghiệm chúng.
Peter Taylor

Các kho của Fedora cung cấp phiên bản 2.10.8, đã hơn hai năm tuổi. Có thể các phiên bản mới hơn nhanh hơn. Tôi tò mò: Mất bao lâu với Microsoft?
Dennis

2
Từ 30 phút đến 39 micro giây. Đó là những gì tôi gọi là tối ưu hóa!
Dennis

1

Chương trình Python Tester

Tôi cho rằng sẽ rất hữu ích khi có một chương trình thử nghiệm có thể xác minh rằng việc triển khai của bạn đang hoạt động. Cả hai tập lệnh bên dưới đều hoạt động với Python 2 hoặc Python 3.

Chương trình kiểm thử ( 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)

Giao thức: Hai chương trình gián điệp được chỉ định trên dòng lệnh sẽ được thực thi. Họ dự kiến ​​sẽ chỉ tương tác mặc dù stdin / stdout. Mỗi chương trình sẽ nhận được số được chỉ định là dòng đầu tiên. Trong mỗi lượt, điệp viên 1 xuất ra số viên đá để ném, điệp viên 2 đọc một số từ stdin (đại diện cho cú ném của điệp viên 1), và sau đó chúng lặp lại (với vị trí đảo ngược). Khi một trong hai điệp viên xác định rằng 26 viên đá đã bị ném, họ dừng lại và đưa ra dự đoán của họ cho số của người khác.

Phiên ví dụ với một spy1 tương thích ( >biểu thị đầu vào cho gián điệp)

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

Nếu bạn chọn một M rất lớn, và phải mất quá nhiều thời gian để chạy, bạn có thể chuyển test(cho testrand(trong dòng cuối cùng để chạy thử nghiệm ngẫu nhiên. Trong trường hợp sau, hãy để chương trình chạy ít nhất vài nghìn thử nghiệm để tăng cường sự tự tin.

Chương trình ví dụ ( spy.py), cho 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.")

Ví dụ sử dụng:

python tester.py 42 'python spy.py spy1' 'python spy.py spy2'

1

Java, M = 2535

OK, đây là thực hiện của tôi. Ở mỗi bước, một điệp viên thực hiện một động thái. Mỗi di chuyển có thể đại diện cho một loạt các mã. Các điệp viên chọn di chuyển phù hợp với mã bí mật của mình. Khi họ ném nhiều đá hơn, phạm vi của các mã có thể giảm cho đến khi, cuối cùng, đối với cả hai điệp viên, chỉ còn một mã duy nhất có thể theo các động thái họ đã làm.

Để khôi phục mã bí mật, bạn có thể phát lại tất cả các di chuyển và tính toán phạm vi mã tương ứng. Cuối cùng, chỉ còn một mã cho mỗi điệp viên, đó là mã bí mật mà anh ta muốn truyền.

Thật không may, thuật toán dựa trên một bảng được tính toán trước lớn với hàng trăm ngàn số nguyên. Phương pháp này không thể được áp dụng về mặt tinh thần với hơn 8-10 viên đá.

Tệp đầu tiên thực hiện thuật toán Spy. Phần tĩnh tính toán trước một codeCountbảng mà sau đó được sử dụng để tính toán từng di chuyển. Phần thứ hai thực hiện 2 thủ tục, một để chọn bao nhiêu viên đá, phần còn lại để phát lại một động tác để giúp xây dựng lại các mã bí mật.

Tệp thứ hai kiểm tra rộng rãi lớp Spy. Phương pháp simulatemô phỏng quá trình. Nó sử dụng lớp Spy để tạo ra một chuỗi các cú ném từ các mã bí mật và sau đó xây dựng lại các mã từ chuỗi đó.

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];
    }
}

NémStones.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...
    }

}

Để tham khảo, mảng codeCount được tính toán trước chứa các giá trị sau:

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]

Điều này liên quan trực tiếp đến bộ Tk của Peter Taylor. Chúng ta có:

(x,y) in Tk  <=>  y <= codeCount[x]

Tôi không nghĩ rằng điều này hoàn toàn đáp ứng thông số kỹ thuật mà không có cách nào để chạy hai điệp viên trong các quy trình riêng biệt và truyền đạt các cú ném mà không chia sẻ quyền truy cập vào các rangelĩnh vực của họ . Nhưng tôi rất thích phương pháp tính bảng của bạn. Bạn có một bằng chứng về sự đúng đắn? Và bạn có quan tâm đến việc cộng tác trên một bài báo thảo luận về vấn đề và tính toán giải pháp của nó không?
Peter Taylor

Phạm vi của spy khác là một chức năng của các bước di chuyển trong quá khứ, vì nó được tính toán trong phương pháp "phát lại". Tôi tin rằng nó là chính xác. Bảng tôi tính toán hoàn toàn giống với bảng bạn đặt Tk. Chuyển đổi bảng trao đổi x và y, tổng là tổng của tất cả các con có thể từ một nút. Tôi đã không chứng minh nó đúng, ngoại trừ việc tôi đã kiểm tra tới 22 viên đá. Tôi đã cố gắng viết một câu trả lời thích hợp cho puzzling.stackexchange, nhưng tôi đã không thể giải thích nó một cách rõ ràng một cách thuyết phục. Và chủ yếu, đó là những gì bạn đã làm.
Florian F

Được. Tôi có thể không có thời gian trong tuần này, nhưng khi tôi ít bận rộn hơn, tôi sẽ cố gắng tìm một bằng chứng cho thấy phương thức thế hệ của bạn tạo ra cùng một bảng với tôi, bởi vì tôi nghĩ nó sẽ là một bổ sung tốt cho những thứ tôi ' đã viết lên rồi.
Peter Taylor

Trên thực tế, nó khá đơn giản: sự tương đương của nó với phương pháp tính toán của tôi đưa ra một bổ đề rằng liên hợp của đa liên kết của hai phân vùng bằng tổng số liên hợp của chúng.
Peter Taylor

(nghiêng đầu) Nhưng tất nhiên rồi! Tại sao tôi không nghĩ về điều đó sớm hơn? :-)
Florian F

0

ksh / zsh, M = 126

Trong hệ thống đơn giản này, mỗi điệp viên ném các chữ số nhị phân cho điệp viên khác. Trong mỗi lần ném, viên đá đầu tiên bị bỏ qua, những viên đá tiếp theo là mỗi bit 0 và viên đá cuối cùng là bit 1. Ví dụ, để ném 20, một điệp viên sẽ ném 4 viên đá (bỏ qua, 0, 2, thêm 4), sau đó ném 3 viên đá (bỏ qua, 8, thêm 16), vì 4 + 16 = 20.

Tập hợp các số không liền kề nhau. 0 đến 126 là trong, nhưng 127 là ra. (Nếu cả hai điệp viên có 127 viên, họ cần 28 viên đá, nhưng họ có 26 viên đá.) Sau đó, 128 đến 158 được đưa vào, 159 ra, 160 đến 174, trong số 175, 182 đến 182, hết 183, 182 ra, 184 đến 186 là trong, 187 là ra, và như vậy.

Chạy trao đổi tự động với ksh spy.sh 125 126hoặc chạy các gián điệp riêng lẻ với ksh spy.sh spy1 125ksh spy.sh spy2 126. Ở đây, kshcó thể là ksh93, pdksh hoặc zsh.

EDIT 14 tháng 6 năm 2014: Khắc phục sự cố với một số đồng xử lý trong zsh. Họ sẽ nhàn rỗi mãi mãi và không thoát ra được, cho đến khi người dùng giết họ.

(( 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
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.