สร้างสายลับคู่หนึ่งที่จะขว้างก้อนหินลงในแม่น้ำ


20

เมื่อเร็ว ๆ นี้ที่Puzzling ที่เพิ่งเปิดตัวใหม่SEมีปัญหาเกี่ยวกับสายลับที่ขว้างก้อนหินไปยังแม่น้ำที่ค่อนข้างท้าทาย:

สายลับสองคนจะต้องผ่านหมายเลขลับสองหมายเลข (หมายเลขหนึ่งต่อสายลับ) ซึ่งไม่มีศัตรูสังเกตเห็น พวกเขาได้ตกลงกันเกี่ยวกับวิธีการทำสิ่งนี้โดยใช้หินที่แยกไม่ออกล่วงหน้าเพียง 26 ก้อน

พวกเขาพบกันที่แม่น้ำที่มีกองหิน 26 ก้อน เริ่มต้นด้วยสายลับแรกพวกเขาผลัดกันขว้างก้อนหินกลุ่มหนึ่งลงไปในแม่น้ำสายลับตัวแรกโยนก้อนหินจำนวนหนึ่งจากนั้นสายที่สองจากนั้นสายที่หนึ่งจากนั้นสายแรกจะกลับมาอีกครั้ง ...

สายลับแต่ละคนจะต้องขว้างหินอย่างน้อยหนึ่งก้อนในตาของเขาจนกว่าก้อนหินทั้งหมดจะหายไป

พวกเขาสังเกตการขว้างและแตกต่างเมื่อไม่มีหินอีกต่อไป พวกเขาเก็บความเงียบตลอดเวลาและไม่มีการแลกเปลี่ยนข้อมูลยกเว้นจำนวนหินที่โยนในแต่ละรอบ

พวกเขาสามารถแลกเปลี่ยนตัวเลขได้สำเร็จอย่างไรถ้าตัวเลขสามารถอยู่ระหว่าง 1 ถึง M ได้?

งานของคุณคือการสร้างคู่ของโปรแกรม, spy1และที่สามารถแก้ปัญหานี้สำหรับเป็นไปได้สูงสุดspy2M

โปรแกรมของคุณแต่ละคนจะใช้ตัวเลขจาก1การที่คุณเลือกMเป็น input จากนั้นspy1จะเอาท์พุทตัวเลขที่แทนจำนวนก้อนหินที่มันขว้างลงไปในแม่น้ำซึ่งจะเป็นอินพุตspy2ซึ่งจะส่งออกตัวเลขที่จะป้อนเข้าspy1และอื่น ๆ จนกว่าจะมีการส่งออกตัวเลขรวม26กัน เมื่อสิ้นสุดการขว้างแต่ละโปรแกรมจะส่งออกหมายเลขที่เชื่อว่าโปรแกรมอื่นมีซึ่งจะต้องตรงกับหมายเลขที่ป้อนเข้าสู่โปรแกรมอื่นจริง

โปรแกรมของคุณต้องทำงานคู่ได้รับคำสั่งเป็นไปได้ทั้งหมดของตัวเลข(i, j)ที่ทั้งสองiและjจะแตกต่างจากการ1M

โปรแกรมที่ใช้งานได้ดีที่สุดMจะเป็นผู้ชนะโดยคำตอบแรกจะถูกโพสต์ นอกจากนี้ผมจะได้รับรางวัลรางวัล 100 ชื่อเสียงเพื่อแก้ปัญหาแรกที่ได้รับการพิสูจน์ในการทำงานสำหรับM >= 2286และ 300 M >= 2535เพื่อแก้ปัญหาแรกที่ได้รับการพิสูจน์ในการทำงานสำหรับ


โซลูชันหมายถึงอัลกอริธึมหรือโปรแกรมซึ่งสร้างชุดการแยกสำหรับแต่ละ (i, j) หรือไม่
klm123

ไม่ใช่โปรแกรมเดียว แต่มีสองโปรแกรม พวกเขาจะต้องสื่อสารอย่างอิสระเช่นเดียวกับในปัญหาของคุณ
Joe Z.

3
เนื่องจากโปรแกรมจะต้องแบ่งปันแผนผังการตัดสินใจของพวกเขาเราจะทำให้มันเป็นหนึ่งโปรแกรมที่จะโต้แย้งว่าสายลับมันคืออะไร?
Peter Taylor

ตราบใดที่คุณสามารถรับประกันได้ว่าสายลับแต่ละสายสื่อสารได้อย่างอิสระและไม่มีการแลกเปลี่ยนข้อมูลเพิ่มเติมระหว่างกัน
Joe Z.

ฉันได้ตรวจสอบอย่างอิสระแล้วว่า 2535 เป็นข้อมูลสูงสุดเชิงทฤษฎีสำหรับปัญหานี้ ฉันค่อนข้างเชื่อมั่นอย่างยิ่งในตอนนี้ว่าไม่มีโปรแกรมใดสามารถทำได้ดีกว่านี้
nneonneo

คำตอบ:


8

C #, M = 2535

สิ่งนี้ใช้ * ระบบที่ฉันอธิบายทางคณิตศาสตร์ในหัวข้อที่กระตุ้นการประกวดครั้งนี้ ฉันรับโบนัส 300 ตัวแทน โปรแกรมทดสอบตัวเองหากคุณรันโดยไม่มีอาร์กิวเมนต์บรรทัดรับคำสั่งหรือใช้--testเป็นอาร์กิวเมนต์บรรทัดคำสั่ง สำหรับสายลับ 1 ทำงานด้วย--spy1และสำหรับสายลับ --spy22 ในแต่ละกรณีใช้หมายเลขที่ฉันควรสื่อสารจาก stdin และจากนั้นจะพ่นผ่าน stdin และ stdout

* อันที่จริงฉันพบว่าการเพิ่มประสิทธิภาพซึ่งสร้างความแตกต่างอย่างมาก (จากหลายนาทีในการสร้างแผนภูมิการตัดสินใจให้น้อยกว่าหนึ่งวินาที) ต้นไม้ที่มันสร้างนั้นเป็นพื้นฐานเดียวกัน แต่ฉันยังคงทำงานเพื่อพิสูจน์สิ่งนั้น ถ้าคุณต้องการดำเนินการโดยตรงของระบบซึ่งผมอธิบายไว้ที่อื่นให้ดูการแก้ไข 2ถึงแม้ว่าคุณอาจต้องการที่จะย้ายกลับเข้าสู่ระบบพิเศษจากMainและดีกว่า Comms TestSpyIOระหว่างด้ายจาก

หากคุณต้องการกรณีทดสอบที่เสร็จสมบูรณ์ในเวลาน้อยกว่าหนึ่งนาทีที่เปลี่ยนแปลงNไป16และการM87

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

คำแนะนำสำหรับผู้ใช้ Linux

คุณจะต้องmono-cscรวบรวม (บนระบบที่ใช้เดเบียนอยู่ในmono-develแพ็คเกจ) และmonoเพื่อเรียกใช้ ( mono-runtimeแพ็คเกจ) แล้วคาถานั้น

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

เป็นต้น


2
นั่นคือ C # หรือไม่ ฉันไม่รู้วิธีเรียกใช้บน Linux
Joe Z.

ตลอดเวลาที่ฉันคิดว่าฉันกำลังทำอะไรผิด ตามที่ปรากฎการสร้างต้นไม้การตัดสินใจใช้เวลาเพียง 30 นาที ... สำหรับการบันทึกสิ่งนี้จะใช้กับ Fedora 20: 1 yum install mono-core(เหมือนรูท) 2. dmcs Puzzle625.cs3.mono Puzzle625.exe --test
Dennis

@Dennis ฉันคิดว่า JIT ของ Mono นั้นไม่ค่อยดีเท่า Microsoft ฉันมีแนวคิดสำหรับการเพิ่มประสิทธิภาพ แต่ฉันยังไม่ได้ทำการทดสอบ
Peter Taylor

ที่เก็บของ Fedora มีเวอร์ชั่น 2.10.8 ซึ่งมีอายุมากกว่าสองปี บางทีเวอร์ชันที่ใหม่กว่าอาจเร็วกว่า ฉันอยากรู้: Microsoft ใช้เวลานานเท่าไหร่?
Dennis

2
จาก 30 นาทีถึง 39 ไมโครวินาที นั่นคือสิ่งที่ฉันเรียกว่าการเพิ่มประสิทธิภาพ!
Dennis

1

โปรแกรม Python Tester

ฉันคิดว่ามันจะมีประโยชน์ที่จะมีโปรแกรมทดสอบที่สามารถตรวจสอบว่าการใช้งานของคุณทำงานได้ดี สคริปต์ทั้งสองด้านล่างใช้งานได้กับ Python 2 หรือ Python 3

โปรแกรมทดสอบ ( 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)

โปรโตคอล: โปรแกรมสอดแนมทั้งสองที่ระบุในบรรทัดคำสั่งจะถูกดำเนินการ พวกเขาคาดว่าจะโต้ตอบเพียงอย่างเดียวแม้ว่า stdin / stdout แต่ละโปรแกรมจะได้รับหมายเลขที่กำหนดเป็นบรรทัดแรกของอินพุต ในแต่ละเทิร์นสายลับ 1 จะแสดงจำนวนของหินที่จะขว้างสายลับ 2 อ่านหมายเลขจาก stdin (แทนการโยนของสายลับที่ 1) จากนั้นพวกเขาก็พูดซ้ำ (ด้วยตำแหน่งที่ตรงกันข้าม) เมื่อสายลับทั้งสองระบุว่ามีการโยนหิน 26 ก้อนพวกเขาจะหยุดและเอาท์พุททายหมายเลขสายลับอื่น

ตัวอย่างเซสชันพร้อมสอดแนมที่เข้ากันได้ 1 ( >หมายถึงอินพุตของสปาย)

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

หากคุณเลือก M มีขนาดใหญ่มากและจะใช้เวลานานเกินไปในการทำงานคุณสามารถสลับtest(สำหรับtestrand(ในบรรทัดสุดท้ายที่จะเรียกใช้การทดสอบแบบสุ่ม ในกรณีหลังให้ปล่อยให้โปรแกรมทำงานอย่างน้อยสองสามพันครั้งเพื่อสร้างความมั่นใจ

ตัวอย่างโปรแกรม ( spy.py) สำหรับ 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.")

ตัวอย่างการใช้งาน:

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

1

Java, M = 2535

ตกลงนี่คือการดำเนินการของฉัน ในแต่ละขั้นตอนสายลับทำให้เคลื่อนที่ การย้ายที่เป็นไปได้แต่ละครั้งจะแสดงช่วงรหัส สายลับเลือกย้ายที่ตรงกับรหัสลับของเขา เมื่อพวกเขาขว้างก้อนหินมากขึ้นช่วงของรหัสที่เป็นไปได้จะลดลงจนกระทั่งในตอนท้ายสำหรับสายลับทั้งคู่จะมีเพียงหนึ่งรหัสเท่านั้นที่เป็นไปได้ตามการเคลื่อนไหวที่พวกเขาทำ

ในการกู้คืนรหัสลับคุณสามารถเล่นซ้ำการเคลื่อนไหวทั้งหมดและคำนวณช่วงรหัสที่เกี่ยวข้อง ในตอนท้ายมีเพียงหนึ่งรหัสเหลือสำหรับสายลับแต่ละอันนั่นคือรหัสลับที่เขาต้องการส่ง

น่าเสียดายที่อัลกอริทึมนั้นอาศัยตารางที่คำนวณล่วงหน้าจำนวนมากซึ่งมีจำนวนเต็มนับแสน วิธีการนี้ใช้ไม่ได้กับจิตใจด้วยหินมากกว่า 8-10 ก้อน

ไฟล์แรกใช้อัลกอริทึมของ Spy ส่วนคงที่ precomputes codeCountตารางที่ใช้ในภายหลังเพื่อคำนวณการย้ายแต่ละครั้ง ส่วนที่สองใช้ 2 ขั้นตอนหนึ่งในการเลือกจำนวนหินที่จะโยนและอีกอันหนึ่งเพื่อเล่นซ้ำการเคลื่อนไหวเพื่อช่วยสร้างรหัสลับใหม่

ไฟล์ที่สองทดสอบคลาส Spy อย่างกว้างขวาง วิธีการsimulateจำลองกระบวนการ มันใช้คลาส Spy เพื่อสร้างลำดับการโยนจากรหัสลับแล้วสร้างรหัสจากลำดับใหม่

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

}

สำหรับการอ้างอิงอาร์เรย์ codeCount ที่คำนวณล่วงหน้ามีค่าต่อไปนี้:

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]

สิ่งนี้เกี่ยวข้องโดยตรงกับชุด Tk ของ Peter Taylor เรามี:

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

ฉันไม่คิดว่ามันจะเป็นไปตามข้อกำหนดโดยไม่มีวิธีเรียกใช้สองสายลับในกระบวนการที่แยกจากกันและสื่อสารการขว้างโดยไม่ต้องแบ่งปันการเข้าถึงrangeเขตข้อมูลของพวกเขา แต่ฉันรู้สึกทึ่งมากกับวิธีการคำนวณตารางของคุณ คุณมีหลักฐานของความถูกต้องหรือไม่? และคุณสนใจที่จะทำงานร่วมกันบนกระดาษที่พูดถึงปัญหาและการคำนวณวิธีแก้ปัญหาหรือไม่?
Peter Taylor

ระยะของสายลับอื่นเป็นฟังก์ชันของการเคลื่อนไหวที่ผ่านมาเนื่องจากคำนวณในวิธี "เล่นซ้ำ" ฉันเชื่อว่ามันถูกต้อง ตารางที่ฉันคำนวณนั้นเหมือนกับที่คุณตั้งค่า Tk การย้ายตารางแลกเปลี่ยน x และ y ผลรวมคือผลรวมของลูกที่เป็นไปได้ทั้งหมดจากโหนด ฉันยังไม่ได้พิสูจน์ว่าถูกต้องยกเว้นว่าได้ทดสอบถึง 22 ก้อน ฉันพยายามเขียนคำตอบที่เหมาะสมสำหรับ puzzling.stackexchange แต่ฉันไม่สามารถอธิบายได้อย่างชัดเจน และส่วนใหญ่เป็นสิ่งที่คุณทำไปแล้ว
Florian F

ตกลง. ฉันอาจไม่ได้มีเวลาในสัปดาห์นี้ แต่เมื่อฉันยุ่งน้อยฉันจะพยายามหาหลักฐานว่าวิธีการสร้างของคุณสร้างตารางเดียวกับฉันเพราะฉันคิดว่ามันจะเป็นการดีนอกเหนือจากสิ่งที่ฉัน ได้เขียนขึ้นแล้ว
Peter Taylor

อันที่จริงมันค่อนข้างง่าย: ความเท่าเทียมกันกับวิธีการคำนวณของฉันลงมาถึงบทแทรกของการรวมกันของมัลติเซ็ตของสองพาร์ติชันเท่ากับผลรวมจุดของการผัน
Peter Taylor

(ส่ายหัว) แต่แน่นอน! ทำไมฉันไม่คิดอย่างนั้นก่อนหน้านี้ :-)
Florian F

0

ksh / zsh, M = 126

ในระบบที่เรียบง่ายนี้สายลับแต่ละอันจะส่งเลขฐานสองไปยังสายลับอื่น ๆ ในการโยนแต่ละครั้งศิลาแรกจะถูกละเว้นหินถัดไปคือแต่ละบิต 0 และหินก้อนสุดท้ายเป็นบิต 1 ตัวอย่างเช่นการโยน 20 สายลับจะขว้างก้อนหิน 4 ก้อน (เพิกเฉย 0, 2, เพิ่ม 4) จากนั้นขว้าง 3 ก้อน (เพิกเฉย 8 เพิ่ม 16) เพราะ 4 + 16 = 20

ชุดของตัวเลขไม่ต่อเนื่องกัน 0 ถึง 126 อยู่ใน แต่ 127 ออก (ถ้าสายลับทั้งคู่มี 127 พวกเขาต้องการ 28 ก้อน แต่มี 26 ก้อน) จากนั้น 128 ถึง 158 อยู่, 159 ออก, 160 ถึง 174 อยู่, 175 ออก, 176 ถึง 182 อยู่, 183 ออก 184 to 186 เข้า, 187 ออกไปเรื่อย ๆ

เรียกใช้การแลกเปลี่ยนโดยอัตโนมัติด้วยksh spy.sh 125 126หรือเรียกบุคคลที่มีสายลับและksh spy.sh spy1 125 ksh spy.sh spy2 126ที่นี่kshสามารถเป็น ksh93, pdksh หรือ zsh

แก้ไข 14 มิถุนายน 2014: แก้ไขปัญหากับบางกระบวนการร่วมใน zsh พวกเขาจะว่างตลอดไปและไม่สามารถออกได้จนกว่าผู้ใช้จะฆ่าพวกเขา

(( 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.