C #, M = 2535
Isso implementa * o sistema que eu descrevi matematicamente no tópico que provocou esse concurso. Eu reivindico o bônus de 300 representantes. O programa autoteste se você o executa sem argumentos de linha de comando ou --test
como argumento de linha de comando; para o espião 1, corra com --spy1
, e para o espião 2 com --spy2
. Em cada caso, pega o número que devo comunicar a partir de stdin e, em seguida, lança através de stdin e stdout.
* Na verdade, encontrei uma otimização que faz uma enorme diferença (de alguns minutos para gerar a árvore de decisão a menos de um segundo); a árvore que gera é fundamentalmente a mesma, mas ainda estou trabalhando em uma prova disso. Se você deseja uma implementação direta do sistema que eu descrevi em outro lugar, consulte a revisão 2 , embora possa querer suportar o log extra Main
e as melhores comunicações entre threads TestSpyIO
.
Se você deseja um caso de teste concluído em menos de um minuto, altere N
para 16
e M
para 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;
}
}
}
Instruções para usuários do Linux
Você precisará mono-csc
compilar (em sistemas baseados no Debian, está no mono-devel
pacote) e mono
executar ( mono-runtime
pacote). Então os encantamentos são
mono-csc -out:codegolf31673.exe codegolf31673.cs
mono codegolf31673.exe --test
etc.