C #, M = 2535
Dies implementiert das System, das ich mathematisch in dem Thread beschrieben habe, der diesen Wettbewerb provoziert hat. Ich beanspruche den 300 Wiederholungsbonus. Das Programm führt einen Selbsttest durch, wenn Sie es entweder ohne Befehlszeilenargumente oder --test
als Befehlszeilenargument ausführen . für Spion 1 laufen mit --spy1
und für Spion 2 mit --spy2
. In jedem Fall nimmt es die Nummer, die ich von stdin mitteilen soll, und führt dann die Würfe über stdin und stdout aus.
* Tatsächlich habe ich eine Optimierung gefunden, die einen großen Unterschied macht (von einigen Minuten zum Generieren des Entscheidungsbaums bis zu weniger als einer Sekunde). Der Baum, den es erzeugt, ist im Grunde derselbe, aber ich arbeite immer noch an einem Beweis dafür. Wenn Sie eine direkte Implementierung des Systems wünschen, das ich an anderer Stelle beschrieben habe, lesen Sie Revision 2 , obwohl Sie möglicherweise die zusätzliche Protokollierung von Main
und die bessere Kommunikation zwischen Threads von zurückportieren möchten TestSpyIO
.
Wenn Sie einen Testfall wünschen, der in weniger als einer Minute abgeschlossen ist, wechseln Sie N
zu 16
und M
zu 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;
}
}
}
Anweisungen für Linux-Benutzer
Sie müssen mono-csc
kompilieren (auf Debian-basierten Systemen ist es im mono-devel
Paket enthalten) und mono
ausführen ( mono-runtime
Paket). Dann sind die Beschwörungsformeln
mono-csc -out:codegolf31673.exe codegolf31673.cs
mono codegolf31673.exe --test
etc.