C ++ 11-6.66109
另一个广度优先搜索实现,仅进行了优化。
它必须与-s选项一起运行。
它的输入完全没有经过消毒,因此错误的跟踪可能会将其变成南瓜。
我使用Microsoft Visual C ++ 2013测试了它,并使用默认的/ O2标志发布了版本(针对速度进行了优化)。
可以使用g ++和Microsoft IDE生成OK。
我的准系统内存分配器是一堆废话,所以不要指望它可与unordered_set
!的其他STL实现一起使用。
#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
表演节目
那种cr脚的C ++语言有一个诀窍,可让您跳圈,仅移动火柴。但是,您可以鞭打它以生成相对快速且内存有效的代码。
散列
这里的关键是为节点提供一个良好的哈希表。到目前为止,这是执行速度的主要因素。GNU和Microsoft的
两个实现unordered_set
产生了30%的执行速度差异(赞成GNU,是的!)。
区别并不令人惊讶,隐藏在后面的大量代码又如何呢unordered_set
?
出于好奇,我对哈希表的最终状态做了一些统计。
两种算法最终得到的存储桶/元素比率几乎相同,但是分区有所不同:
对于290x290的平局决胜游戏,GNU每个非空存储桶平均获得1.5个元素,而Microsoft的平均得分为5.8(!)。
看起来我的哈希函数不是很好地被Microsoft随机分配...我想知道Redmond的家伙是否真的对他们的STL进行了基准测试,或者我的用例仅偏爱GNU实现...
当然,我的哈希函数远非最佳。我本可以使用基于多个移位/倍数的常规整数混合,但是哈希有效的函数需要花费一些时间来计算。
与插入的数量相比,哈希表查询的数量似乎非常高。例如,在290x290的平局决胜游戏中,您有大约360万次插入操作,可查询2270万次查询。
在这种情况下,次优但快速的散列会产生更好的性能。
内存分配
第二,提供有效的内存分配器。它使性能提高了约30%。值得添加的废话代码是否值得争论:)。
当前版本每个节点使用40到55个字节。
对于一个节点,功能数据需要24个字节(4个坐标和2个指针)。
由于疯狂的100.000行测试用例,必须将坐标存储在4个字节的字中,否则可以通过使用短裤(最大坐标值为32767)获得8个字节。其余字节主要由无序集的哈希表消耗。这意味着数据处理实际上比“有用的”有效负载消耗更多。
最终获胜者是...
在Win7下的PC上,最坏的版本(即Microsoft编译)在大约2.2秒内解决了决胜局(案例23,290x290),并消耗了约185 Mb的内存。
为了进行比较,当前的领导者(user2357112的python代码)花费了30多秒钟的时间,消耗了大约780 Mb。
控制器问题
我不太确定我是否可以使用Ruby进行编码以挽救生命。
但是,我发现并破解了控制器代码中的两个问题:
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";
?>