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 --test
tư cách là đối số dòng lệnh; cho gián điệp 1, chạy với --spy1
và 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ừ Main
và 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 N
thành 16
và M
thà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-csc
phải biên dịch (trên các hệ thống dựa trên Debian, nó có trong mono-devel
gói) và mono
để chạy ( mono-runtime
gói). Sau đó, câu thần chú là
mono-csc -out:codegolf31673.exe codegolf31673.cs
mono codegolf31673.exe --test
v.v.