C ++ 11-6.66109
さらに最適化された、幅広の最初の検索実装。
-sオプションを使用して実行する必要があります。
その入力は完全にサニタイズされないため、間違ったトラックはそれをカボチャに変える可能性があります。
Microsoft Visual C ++ 2013、リリースビルドでデフォルトの/ O2フラグを使用してテストしました(速度を最適化する)。
g ++およびMicrosoft IDEを使用して、ビルドは問題ありません。
私のベアボーンメモリアロケータはがらくたなので、他のSTL実装で動作することを期待しないでunordered_set
ください!
#include <cstdint>
#include <iostream>
#include <fstream>
#include <sstream>
#include <queue>
#include <unordered_set>
#define MAP_START 'S'
#define MAP_WALL '#'
#define MAP_GOAL '*'
#define NODE_CHUNK_SIZE 100 // increasing this will not improve performances
#define VISIT_CHUNK_SIZE 1024 // increasing this will slightly reduce mem consumption at the (slight) cost of speed
#define HASH_POS_BITS 8 // number of bits for one coordinate
#define HASH_SPD_BITS (sizeof(size_t)*8/2-HASH_POS_BITS)
typedef int32_t tCoord; // 32 bits required to overcome the 100.000 cells (insanely) long challenge
// basic vector arithmetics
struct tPoint {
tCoord x, y;
tPoint(tCoord x = 0, tCoord y = 0) : x(x), y(y) {}
tPoint operator+ (const tPoint & p) { return tPoint(x + p.x, y + p.y); }
tPoint operator- (const tPoint & p) { return tPoint(x - p.x, y - p.y); }
bool operator== (const tPoint & p) const { return p.x == x && p.y == y; }
};
// a barebone block allocator. Improves speed by about 30%
template <class T, size_t SIZE> class tAllocator
{
T * chunk;
size_t i_alloc;
size_t m_alloc;
public:
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef std::size_t size_type;
typedef value_type& reference;
typedef const value_type& const_reference;
tAllocator() { m_alloc = i_alloc = SIZE; }
template <class U> tAllocator(const tAllocator<U, SIZE>&) { m_alloc = i_alloc = SIZE; }
template <class U> struct rebind { typedef tAllocator<U, SIZE> other; };
pointer allocate(size_type n, const_pointer = 0)
{
if (n > m_alloc) { i_alloc = m_alloc = n; } // grow max size if request exceeds capacity
if ((i_alloc + n) > m_alloc) i_alloc = m_alloc; // dump current chunk if not enough room available
if (i_alloc == m_alloc) { chunk = new T[m_alloc]; i_alloc = 0; } // allocate new chunk when needed
T * mem = &chunk[i_alloc];
i_alloc += n;
return mem;
}
void deallocate(pointer, size_type) { /* memory is NOT released until process exits */ }
void construct(pointer p, const value_type& x) { new(p)value_type(x); }
void destroy(pointer p) { p->~value_type(); }
};
// a node in our search graph
class tNode {
static tAllocator<tNode, NODE_CHUNK_SIZE> mem; // about 10% speed gain over a basic allocation
tNode * parent;
public:
tPoint pos;
tPoint speed;
static tNode * alloc (tPoint pos, tPoint speed, tNode * parent) { return new (mem.allocate(1)) tNode(pos, speed, parent); }
tNode (tPoint pos = tPoint(), tPoint speed = tPoint(), tNode * parent = nullptr) : parent(parent), pos(pos), speed(speed) {}
bool operator== (const tNode& n) const { return n.pos == pos && n.speed == speed; }
void output(void)
{
std::string output;
tPoint v = this->speed;
for (tNode * n = this->parent ; n != nullptr ; n = n->parent)
{
tPoint a = v - n->speed;
v = n->speed;
std::ostringstream ss; // a bit of shitty c++ text I/O to print elements in reverse order
ss << a.x << ' ' << a.y << '\n';
output = ss.str() + output;
}
std::cout << output;
}
};
tAllocator<tNode, NODE_CHUNK_SIZE> tNode::mem;
// node queueing and storing
static int num_nodes = 0;
class tNodeJanitor {
// set of already visited nodes. Block allocator improves speed by about 20%
struct Hasher { size_t operator() (tNode * const n) const
{
int64_t hash = // efficient hashing is the key of performances
((int64_t)n->pos.x << (0 * HASH_POS_BITS))
^ ((int64_t)n->pos.y << (1 * HASH_POS_BITS))
^ ((int64_t)n->speed.x << (2 * HASH_POS_BITS + 0 * HASH_SPD_BITS))
^ ((int64_t)n->speed.y << (2 * HASH_POS_BITS + 1 * HASH_SPD_BITS));
return (size_t)((hash >> 32) ^ hash);
//return (size_t)(hash);
}
};
struct Equalizer { bool operator() (tNode * const n1, tNode * const n2) const
{ return *n1 == *n2; }};
std::unordered_set<tNode *, Hasher, Equalizer, tAllocator<tNode *, VISIT_CHUNK_SIZE>> visited;
std::queue<tNode *> queue; // currently explored nodes queue
public:
bool empty(void) { return queue.empty(); }
tNode * dequeue() { tNode * n = queue.front(); queue.pop(); return n; }
tNode * enqueue_if_new (tPoint pos, tPoint speed = tPoint(0,0), tNode * parent = nullptr)
{
tNode signature (pos, speed);
tNode * n = nullptr;
if (visited.find (&signature) == visited.end()) // the classy way to check if an element is in a set
{
n = tNode::alloc(pos, speed, parent);
queue.push(n);
visited.insert (n);
num_nodes++;
}
return n;
}
};
// map representation
class tMap {
std::vector<char> cell;
tPoint dim; // dimensions
public:
void set_size(tCoord x, tCoord y) { dim = tPoint(x, y); cell.resize(x*y); }
void set(tCoord x, tCoord y, char c) { cell[y*dim.x + x] = c; }
char get(tPoint pos)
{
if (pos.x < 0 || pos.x >= dim.x || pos.y < 0 || pos.y >= dim.y) return MAP_WALL;
return cell[pos.y*dim.x + pos.x];
}
void dump(void)
{
for (int y = 0; y != dim.y; y++)
{
for (int x = 0; x != dim.x; x++) fprintf(stderr, "%c", cell[y*dim.x + x]);
fprintf(stderr, "\n");
}
}
};
// race manager
class tRace {
tPoint start;
tNodeJanitor border;
static tPoint acceleration[9];
public:
tMap map;
tRace ()
{
int target;
tCoord sx, sy;
std::cin >> target >> sx >> sy;
std::cin.ignore();
map.set_size (sx, sy);
std::string row;
for (int y = 0; y != sy; y++)
{
std::getline(std::cin, row);
for (int x = 0; x != sx; x++)
{
char c = row[x];
if (c == MAP_START) start = tPoint(x, y);
map.set(x, y, c);
}
}
}
// all the C++ crap above makes for a nice and compact solver
tNode * solve(void)
{
tNode * initial = border.enqueue_if_new (start);
while (!border.empty())
{
tNode * node = border.dequeue();
tPoint p = node->pos;
tPoint v = node->speed;
for (tPoint a : acceleration)
{
tPoint nv = v + a;
tPoint np = p + nv;
char c = map.get(np);
if (c == MAP_WALL) continue;
if (c == MAP_GOAL) return new tNode (np, nv, node);
border.enqueue_if_new (np, nv, node);
}
}
return initial; // no solution found, will output nothing
}
};
tPoint tRace::acceleration[] = {
tPoint(-1,-1), tPoint(-1, 0), tPoint(-1, 1),
tPoint( 0,-1), tPoint( 0, 0), tPoint( 0, 1),
tPoint( 1,-1), tPoint( 1, 0), tPoint( 1, 1)};
#include <ctime>
int main(void)
{
tRace race;
clock_t start = clock();
tNode * solution = race.solve();
std::cerr << "time: " << (clock()-start)/(CLOCKS_PER_SEC/1000) << "ms nodes: " << num_nodes << std::endl;
solution->output();
return 0;
}
結果
No. Size Target Score Details
-------------------------------------------------------------------------------------
1 37 x 1 36 0.22222 Racer reached goal at ( 36, 0) in 8 turns.
2 38 x 1 37 0.24324 Racer reached goal at ( 37, 0) in 9 turns.
3 33 x 1 32 0.25000 Racer reached goal at ( 32, 0) in 8 turns.
4 10 x 10 10 0.40000 Racer reached goal at ( 7, 7) in 4 turns.
5 9 x 6 8 0.37500 Racer reached goal at ( 6, 0) in 3 turns.
6 15 x 7 16 0.37500 Racer reached goal at ( 12, 4) in 6 turns.
7 17 x 8 16 0.31250 Racer reached goal at ( 14, 0) in 5 turns.
8 19 x 13 18 0.27778 Racer reached goal at ( 0, 11) in 5 turns.
9 60 x 10 107 0.14953 Racer reached goal at ( 0, 6) in 16 turns.
10 31 x 31 106 0.23585 Racer reached goal at ( 27, 0) in 25 turns.
11 31 x 31 106 0.24528 Racer reached goal at ( 15, 15) in 26 turns.
12 50 x 20 50 0.24000 Racer reached goal at ( 49, 10) in 12 turns.
13 100 x 100 2600 0.01385 Racer reached goal at ( 50, 0) in 36 turns.
14 79 x 63 242 0.24380 Racer reached goal at ( 3, 42) in 59 turns.
15 26 x 1 25 0.32000 Racer reached goal at ( 25, 0) in 8 turns.
16 17 x 1 19 0.52632 Racer reached goal at ( 16, 0) in 10 turns.
17 50 x 1 55 0.34545 Racer reached goal at ( 23, 0) in 19 turns.
18 10 x 7 23 0.34783 Racer reached goal at ( 1, 3) in 8 turns.
19 55 x 55 45 0.17778 Racer reached goal at ( 50, 26) in 8 turns.
20 101 x 100 100 0.14000 Racer reached goal at ( 99, 99) in 14 turns.
21 100000 x 1 1 1.00000 Racer reached goal at ( 0, 0) in 1 turns.
22 50 x 50 200 0.05500 Racer reached goal at ( 47, 46) in 11 turns.
23 290 x 290 1160 0.16466 Racer reached goal at ( 269, 265) in 191 turns.
-------------------------------------------------------------------------------------
TOTAL SCORE: 6.66109
公演
そのくだらないC ++言語には、マッチスティックを動かすためだけにフープを飛び越えるコツがあります。ただし、比較的高速でメモリ効率の高いコードを生成するためにそれをホイップできます。
ハッシング
ここで重要なのは、ノードに適切なハッシュテーブルを提供することです。それが実行速度の圧倒的な要因です。
2つの実装unordered_set
(GNUとMicrosoft)では、実行速度が30%違いました(GNUを支持して、やった!)。
違いは驚くべきことではありませんunordered_set
。コードのトラックが背後に隠されているのです。
好奇心から、ハッシュテーブルの最終状態についていくつかの統計を行いました。
両方のアルゴリズムは最終的にほぼ同じバケット/要素比になりますが、再分割は異なります
。290x290タイブレーカーでは、GNUは空でないバケットごとに平均1.5要素を取得しますが、Microsoftは5.8(!)です。
私のハッシュ関数はMicrosoftによってあまりうまくランダム化されていないように見えます...レドモンドの人たちは本当にSTLのベンチマークを行ったのでしょうか、それとも私のユースケースはGNU実装を好むのでしょうか...
確かに、私のハッシュ関数は最適にはほど遠い。複数のシフト/ミュールに基づいた通常の整数ミキシングを使用することもできましたが、ハッシュ効率の良い関数は計算に時間がかかります。
ハッシュテーブルクエリの数は、挿入数と比較して非常に多いようです。たとえば、290x290タイブレーカーでは、2270万件のクエリに対して約360万件の挿入があります。
これに関連して、最適ではないが高速なハッシュはパフォーマンスを向上させます。
メモリ割り当て
効率的なメモリアロケーターを提供するのは2番目です。パフォーマンスが約30%向上しました。追加されたがらくたコードの価値があるかどうかは議論の余地があります:)。
現在のバージョンでは、ノードごとに40〜55バイトを使用します。
機能データには、ノードに24バイトが必要です(4つの座標と2つのポインター)。
異常な100.000行のテストケースのため、座標は4バイトワードで保存する必要があります。そうしないと、ショート(最大座標値32767)を使用して8バイトを取得できます。残りのバイトは、ほとんど順序付けられていないセットのハッシュテーブルによって消費されます。これは、データ処理が実際に「有用な」ペイロードよりも少し多く消費することを意味します。
そして勝者は...
Win7のPCでは、タイブレーカー(ケース23、290x290)は最悪のバージョン(つまり、Microsoftがコンパイル)で約2.2秒で解決され、メモリ消費量は約185 Mbです。
比較のために、現在のリーダー(user2357112によるpythonコード)は30秒以上かかり、約780 Mbを消費します。
コントローラーの問題
自分の命を救うためにRubyでコーディングできるかどうかはよくわかりません。
ただし、コントローラーコードから2つの問題を見つけてハッキングしました。
1)地図読み込み track.rb
ruby 1.9.3がインストールされていると、トラックリーダーはshift.to_i
が利用できないことを嘆きますstring.lines
。
オンラインのRubyドキュメントを少し調べた後、私は文字列をあきらめ、代わりに中間配列を使用しました(ファイルの先頭):
def initialize(string)
@track = Array.new;
string.lines.each do |line|
@track.push (line.chomp)
end
2)幽霊を殺す controller.rb
他のポスターがすでに述べたように、コントローラーは、すでに終了したプロセスを強制終了しようとすることがあります。これらの不快なエラー出力を回避するために、次のように例外を処理しました(134行目付近):
if silent
begin # ;!;
Process.kill('KILL', racer.pid)
rescue Exception => e
end
テストケース
BFSソルバーのブルートフォースアプローチに打ち勝つために、最悪のトラックは100.000セルマップの反対です。つまり、可能な限り最初から可能な限り完全にフリーなエリアです。
この例では、左上隅にゴールがあり、右下に開始点がある100x400のマップ。
このマップには28ターンで解決策がありますが、BFSソルバーは何百万もの州を探索してそれを見つけます(訪問した州は10.022.658州を数え、約12秒かかり、600 Mbでピークに達しました!)。
290x290タイブレーカーの半分以下の面積で、約3倍のノード訪問が必要です。一方、ヒューリスティック/ A *ベースのソルバーは簡単に無効にする必要があります。
30
100 400
*...................................................................................................
....................................................................................................
< 400 lines in all >
....................................................................................................
....................................................................................................
...................................................................................................S
ボーナス:同等の(ただしやや効率が悪い)PHPバージョン
これは、固有の言語の遅さからC ++を使用するように説得される前に、私が始めたものです。
PHPの内部ハッシュテーブルは、少なくともこの特定のケースではPythonほど効率的ではないようです:)。
<?php
class Trace {
static $file;
public static $state_member;
public static $state_target;
static function msg ($msg)
{
fputs (self::$file, "$msg\n");
}
static function dump ($var, $msg=null)
{
ob_start();
if ($msg) echo "$msg ";
var_dump($var);
$dump=ob_get_contents();
ob_end_clean();
fputs (self::$file, "$dump\n");
}
function init ($fname)
{
self::$file = fopen ($fname, "w");
}
}
Trace::init ("racer.txt");
class Point {
public $x;
public $y;
function __construct ($x=0, $y=0)
{
$this->x = (float)$x;
$this->y = (float)$y;
}
function __toString ()
{
return "[$this->x $this->y]";
}
function add ($v)
{
return new Point ($this->x + $v->x, $this->y + $v->y);
}
function vector_to ($goal)
{
return new Point ($goal->x - $this->x, $goal->y - $this->y);
}
}
class Node {
public $posx , $posy ;
public $speedx, $speedy;
private $parent;
public function __construct ($posx, $posy, $speedx, $speedy, $parent)
{
$this->posx = $posx;
$this->posy = $posy;
$this->speedx = $speedx;
$this->speedy = $speedy;
$this->parent = $parent;
}
public function path ()
{
$res = array();
$v = new Point ($this->speedx, $this->speedy);
for ($node = $this->parent ; $node != null ; $node = $node->parent)
{
$nv = new Point ($node->speedx, $node->speedy);
$a = $nv->vector_to ($v);
$v = new Point ($node->speedx, $node->speedy);
array_unshift ($res, $a);
}
return $res;
}
}
class Map {
private static $target; // maximal number of turns
private static $time; // time available to solve
private static $sx, $sy; // map dimensions
private static $cell; // cells of the map
private static $start; // starting point
private static $acceleration; // possible acceleration values
public static function init ()
{
// read map definition
self::$target = trim(fgets(STDIN));
list (self::$sx, self::$sy) = explode (" ", trim(fgets(STDIN)));
self::$cell = array();
for ($y = 0 ; $y != self::$sy ; $y++) self::$cell[] = str_split (trim(fgets(STDIN)));
self::$time = trim(fgets(STDIN));
// get starting point
foreach (self::$cell as $y=>$row)
{
$x = array_search ("S", $row);
if ($x !== false)
{
self::$start = new Point ($x, $y);
Trace::msg ("start ".self::$start);
break;
}
}
// compute possible acceleration values
self::$acceleration = array();
for ($x = -1 ; $x <= 1 ; $x++)
for ($y = -1 ; $y <= 1 ; $y++)
{
self::$acceleration[] = new Point ($x, $y);
}
}
public static function solve ()
{
$now = microtime(true);
$res = array();
$border = array (new Node (self::$start->x, self::$start->y, 0, 0, null));
$present = array (self::$start->x." ".self::$start->y." 0 0" => 1);
while (count ($border))
{
if ((microtime(true) - $now) > 1)
{
Trace::msg (count($present)." nodes, ".round(memory_get_usage(true)/1024)."K");
$now = microtime(true);
}
$node = array_shift ($border);
//Trace::msg ("node $node->pos $node->speed");
$px = $node->posx;
$py = $node->posy;
$vx = $node->speedx;
$vy = $node->speedy;
foreach (self::$acceleration as $a)
{
$nvx = $vx + $a->x;
$nvy = $vy + $a->y;
$npx = $px + $nvx;
$npy = $py + $nvy;
if ($npx < 0 || $npx >= self::$sx || $npy < 0 || $npy >= self::$sy || self::$cell[$npy][$npx] == "#")
{
//Trace::msg ("invalid position $px,$py $vx,$vy -> $npx,$npy");
continue;
}
if (self::$cell[$npy][$npx] == "*")
{
Trace::msg ("winning position $px,$py $vx,$vy -> $npx,$npy");
$end = new Node ($npx, $npy, $nvx, $nvy, $node);
$res = $end->path ();
break 2;
}
//Trace::msg ("checking $np $nv");
$signature = "$npx $npy $nvx $nvy";
if (isset ($present[$signature])) continue;
//Trace::msg ("*** adding $np $nv");
$border[] = new Node ($npx, $npy, $nvx, $nvy, $node);
$present[$signature] = 1;
}
}
return $res;
}
}
ini_set("memory_limit","1000M");
Map::init ();
$res = Map::solve();
//Trace::dump ($res);
foreach ($res as $a) echo "$a->x $a->y\n";
?>