Vectoryへ!–ベクターレーシンググランプリ


39

ユーザーCarpetPython がこの問題に関する新しい見解を投稿しまし。検索スペースが増加したため、ヒューリスティックなソリューションにより大きな焦点を当てています。個人的には、この挑戦​​は私の挑戦よりもずっといいと思うので、試してみてください!

ベクトルレースは、ペンと四角い紙で遊ぶことができる中毒性のゲームです。紙の上に任意の競馬場を描き、開始と終了を定義してから、ターンベースの方法でポイントサイズの車を操縦します。できるだけ早く終わらせますが、壁につかないように注意してください!

トラック

  • マップは2次元のグリッドで、各セルには整数座標があります。
  • グリッドセル上を移動します。
  • 各グリッドセルは、トラックの一部であるか、壁です。
  • 正確に1つのトラックセルが開始座標です。
  • 少なくとも1つのトラックセルが目標として指定されています。これらのいずれかに着陸すると、レースが完了します。複数の目標セルが接続されているとは限りません。

車の操縦

あなたの車は与えられた座標から速度ベクトルで始まり(0, 0)ます。各ターンで、ベロシティの各コンポーネントを調整する±1か、そのままにすることができます。次に、結果の速度ベクトルが車の位置に追加されます。

写真が役立つ場合があります!赤い丸はあなたの最後のターンの位置でした。青い円は現在の位置です。速度は、赤から青の円へのベクトルです。このターンでは、速度の調整方法に応じて、緑色の円のいずれかに移動できます。

                                    ここに画像の説明を入力してください

あなたがいる場合土地の壁に、あなたはすぐに失います。

あなたのタスク

あなたはそれを推測しました:入力として競馬場を与えられたプログラムを書いて、できるだけ少ないターンでゴールセルの1つに車を操縦します。ソリューションは、任意のトラックを適切に処理でき、提供されたテストケースに対して特に最適化されていない必要があります。

入力

プログラムが呼び出されたら、stdinから読み取ります。

target
n m
[ASCII representation of an n x m racetrack]
time

targetは、トラックを完了するために取ることができる最大ターン数であり、トラックのtime合計時間予算(秒単位)(必ずしも整数ではない)です。タイミングの詳細については、以下を参照してください。

改行区切りのトラックには、次の文字が使用されます。

  • # –壁
  • S- スタート
  • *- 目標
  • . –他のすべてのトラックセル(道路)

n x m提供されたグリッドの外側のすべてのセルは、壁であると暗示されています。

座標原点は左上隅にあります。

以下に簡単な例を示します。

8
4.0
9 6
###...***
###...***
###...***
......###
S.....###
......###

0から始まるインデックスを使用すると、開始座標はになります(0,4)

一挙一動あなたは、入力を受け取ります。

x y
u v
time

ここでxyuv全て0ベースの整数です。(x,y)は現在の位置で(u,v)あり、現在の速度です。x+uおよび/またはy+v範囲外である可能性があることに注意してください。

time秒単位で、あなたの時間の予算に残っているものは何でもです。これを無視してください。これは、実際に制限時間内に実装を行いたい参加者のみが対象です。

ゲームが終了すると(壁に着地した、境界を越えて移動した、targetターンを超えた、時間切れになった、またはゴールに到達したため)、空のラインが表示されます。

出力

各ターンで、stdoutに書き込みます。

Δu Δv

どこΔuΔvは、それぞれの一つです-101。これは(u,v)、新しい位置を決定するために追加されます。明確にするために、方向は次のとおりです

(-1,-1) ( 0,-1) ( 1,-1)
(-1, 0) ( 0, 0) ( 1, 0)
(-1, 1) ( 0, 1) ( 1, 1)

上記の例の最適なソリューションは

1 0
1 -1
1 0

コントローラは自分自身をstderrにアタッチしないため、ボットの開発中に必要になる可能性のあるあらゆる種類のデバッグ出力に自由に使用できます。ただし、送信されたコードのこのような出力は削除またはコメントアウトしてください。

ボットが1ターンごとに応答するのに0.5秒かかる場合があります。時間がかかるターンの場合、(トラックごとの)タイムバジェットはtarget/2数秒になります。ターンに0.5秒以上かかるたびに、時間予算から追加時間が差し引かれます。時間の予算がゼロになると、現在のレースは中止されます。

New:実用的な理由から、メモリの制限を設定する必要があります(適切なトラックサイズの場合、メモリは時間よりも制限があるようです)。したがって、Process ExplorerによってPrivate Bytesとして測定されたレーサーが1GBを超えるメモリを使用するテスト実行を中止する必要があります。

得点

20トラックのベンチマークがあります。各トラックについて:

  • トラックを完了すると、スコアはゴールセルに到達するために必要な動きの数をで割ったものになりtargetます。
  • 時間や記憶が足りなくなったりtargetターンが経過する前にゴールに到達しなかったり、いつでも壁や境界の外に着地した場合、スコアは2です。
  • プログラムが決定論的でない場合、スコアはそのトラックでの10回の実行の平均です(回答にこれを記載してください)。

総合スコアは、個々のトラックスコアの合計です。最低スコアが勝ちます!

さらに、すべての参加者は、追加のベンチマークトラックを提供することができ(これを強くお勧めします)、これを公式リストに追加します。以前の回答は、この新しいトラックを含めて再評価されます。これは、既存のテストケースにあまりにも厳密に調整されたソリューションがないことを確認し、見逃したかもしれない興味深いエッジケースを説明するためです(ただし、見つけられるかもしれません)。

タイブレーク

すでに最適な解決策が存在するので、これがおそらく参加者のスコアの主な要因になるでしょう。

タイが存在する場合(複数の回答がすべてのトラックを最適に解決するか、そうでない場合)、追加の(より大きな)テストケースを追加してタイを解除します。これらのタイブレーカーを作成するときに人間のバイアスを回避するために、これらは固定れた方法で生成されます。

  • 私は、辺の長さを増加させるnことにより、10このように生成された最後のトラックに比べて。(ネクタイを壊さない場合、サイズをスキップできます。)
  • 基本はこのベクターグラフィックです
  • これは、このMathematicaスニペットを使用して目的の解像度でラスタライズされます。
  • 開始点は左上隅です。特に、トラックのその端の一番上の行の一番左のセルになります。
  • 目標は右下です。特に、トラックの最後の一番下の行の一番右のセルになります。
  • target意志4*n

最初のベンチマークの最終トラックは、このようにすでに生成されていn = 50ます。

コントローラー

提出物をテストするプログラムはRubyで記述されており、使用するベンチマークファイルと共にGitHubで見つけることができます。そこに呼び出されるボットの例もありますrandomracer.rbが、これは単にランダムな動きを選択します。その基本構造をボットの出発点として使用して、通信がどのように機能するかを確認できます。

次のように、選択したトラックファイルに対して独自のボットを実行できます。

ruby controller.rb track_file_name command to run your racer

例えば

ruby controller.rb benchmark.txt ruby randomracer.rb

リポジトリには、2つのクラスPoint2Dとが含まれますTrack。提出物がRubyで書かれている場合、あなたの便宜のためにそれらを自由に再利用してください。

コマンドラインスイッチ

あなたは、コマンドラインスイッチを追加することができ-v-s-tベンチマークのファイル名の前に。複数のスイッチを使用する場合は、たとえばを実行することもできます-vs。これは彼らがすることです:

-v (冗長):これを使用して、コントローラーからもう少しデバッグ出力を生成します。

-s (サイレント):自分の位置と速度を追跡し、時間の予算を気にしない場合は、このフラグを使用して、各ターン(送信に送信される)の3行の出力をオフにできます。

-t(トラック):テストする個々のトラックを選択できます。たとえば-t "1,2,5..8,15"、トラック1、2、5、6、7、8、15のみをテストします。この機能とオプションパーサーについてVenteroに感謝します。

あなたの提出

要約すると、あなたの答えに以下を含めてください:

  • あなたのスコア。
  • ランダム性を使用している場合は、これを明記してください。複数の実行でスコアを平均化できます。
  • 提出用のコード。
  • Windows 8マシンで実行される、選択した言語の無料のコンパイラーまたはインタープリターの場所。
  • 必要に応じてコンパイル手順。
  • 送信を実行するためのWindowsコマンドライン文字列。
  • 提出に-sフラグが必要かどうか。
  • (オプション)ベンチマークに追加される新しい解決可能なトラック。targetあなたのトラックに合ったものを手動で決定します。トラックがベンチマークに追加されたら、回答から編集します。別のトラックを要求する権利を留保します(不均衡に大きなトラックを追加する場合、トラックにわいせつなASCIIアートを含める場合など)。ベンチマークセットにテストケースを追加する場合、回答のトラックをベンチマークファイルのトラックへのリンクに置き換えて、この投稿の混乱を減らします。

ご覧のとおり、すべての提出物をWindows 8マシンでテストします。提出物をWindowsで実行する方法がまったくない場合は、Ubuntu VMで試すこともできます。ただし、これはかなり遅くなります。そのため、時間制限を最大限にしたい場合は、プログラムがWindowsで実行されることを確認してください。

最高のドライバーがベクトル化されますように!

しかし、は遊びたい!

ゲームを自分で試して、より良い感触を得たい場合は、この実装があります。そこで使用されているルールはもう少し洗練されていますが、有用であるほど十分に似ていると思います。

リーダーボード

最終更新日: 2014年1月9日、午前21時29分UTCの
ベンチマークでのトラック: 25
タイブレーカーサイズ: 290、440

  1. 6.86688 – 黒井猫
  2. 8.73108- user2357112-2回目の送信
  3. 9.86627 – nneonneo
  4. 10.66109 – user2357112-最初の提出
  5. 12.49643 – レイ
  6. 40.0759 – pseudonym117(確率的)

詳細なテスト結果。(確率的提出のスコアは個別に決定されています。)

回答:


5

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";
?>

erf ...私のベアボーンアロケータは、少しばかりベアボーンです。次に、必要ながらくたを追加して、g ++で動作するようにします。ごめんなさい

OK、それは修正されました。g ++バージョンでも、実際には約30%速く動作します。現在、stderrにいくつかの統計を出力します。(ソースの最後の行から)コメントしてください。失礼してまたすみません。

申し分なく、今は動作し、あなたのスコアを再現しました。すごく速い!:)テストケースをベンチマークに追加しますが、ターゲットを400に変更します。これは、他のすべてのターゲット(タイブレーカーを除く)の決定方法に沿っているためです。他のすべての投稿を再実行したら、メインの投稿を更新します。
マーティンエンダー14

結果を更新しました。他のすべての提出物がテストトラックのメモリ制限を超えるため、タイブレーカーは必要ありませんでした。おめでとうございます!:)
マーティンエンダー14

ありがとう。実際、この挑戦​​は私にこれらのSTLハッシュテーブルを掘り下げる機会を与えました。私はC ++の根性が嫌いですが、私の好奇心に殺されざるを得ません。ニャー!:)。

10

C ++、5.4(決定論的、最適)

動的プログラミングソリューション。おそらく最適。非常に高速:20秒すべてのテストケースを0.2秒で解決します。64ビットマシンでは特に高速である必要があります。ボードが各方向で32,000未満の場所にあると仮定します(これは正しいことです)。

このレーサーは少し珍しいです。開始ラインで最適なパスを計算し、その後、計算されたパスを即座に実行します。時間制御を無視し、時間通りに最適化ステップを完了することができると仮定します(合理的な最新のハードウェアのいずれにも当てはまります)。大きすぎるマップでは、レーサーがセグメンテーション違反を起こす可能性がわずかにあります。セグメンテーション違反を納得させることができれば、ブラウニーポイントが得られます。明示的なループを使用するように修正します。

でコンパイルしg++ -O3ます。C ++ 11(for <unordered_map>)が必要になる場合があります。実行するには、コンパイル済みの実行可能ファイルを実行するだけです(フラグやオプションはサポートされていません。すべての入力は標準入力で行われます)。

#include <unordered_map>
#include <iostream>
#include <string>
#include <vector>
#include <sstream>

#include <cstdint>

#define MOVES_INF (1<<30)

union state {
    struct {
        short px, py, vx, vy;
    };
    uint64_t val;
};

struct result {
    int nmoves;
    short dvx, dvy;
};

typedef std::unordered_map<uint64_t, result> cache_t;
int target, n, m;
std::vector<std::string> track;
cache_t cache;

static int solve(uint64_t val) {
    cache_t::iterator it = cache.find(val);
    if(it != cache.end())
        return it->second.nmoves;

    // prevent recursion
    result res;
    res.nmoves = MOVES_INF;
    cache[val] = res;

    state cur;
    cur.val = val;
    for(int dvx = -1; dvx <= 1; dvx++) for(int dvy = -1; dvy <= 1; dvy++) {
        state next;
        next.vx = cur.vx + dvx;
        next.vy = cur.vy + dvy;
        next.px = cur.px + next.vx;
        next.py = cur.py + next.vy;
        if(next.px < 0 || next.px >= n || next.py < 0 || next.py >= m)
            continue;
        char c = track[next.py][next.px];
        if(c == '*') {
            res.nmoves = 1;
            res.dvx = dvx;
            res.dvy = dvy;
            break;
        } else if(c == '#') {
            continue;
        } else {
            int score = solve(next.val) + 1;
            if(score < res.nmoves) {
                res.nmoves = score;
                res.dvx = dvx;
                res.dvy = dvy;
            }
        }
    }

    cache[val] = res;
    return res.nmoves;
}

bool solve_one() {
    std::string line;
    float time;

    std::cin >> target;
    // std::cin >> time; // uncomment to use "time" control
    std::cin >> n >> m;
    if(!std::cin)
        return false;
    std::cin.ignore(); // skip newline at end of "n m" line

    track.clear();
    track.reserve(m);

    for(int i=0; i<m; i++) {
        std::getline(std::cin, line);
        track.push_back(line);
    }

    cache.clear();

    state cur;
    cur.vx = cur.vy = 0;
    for(int y=0; y<m; y++) for(int x=0; x<n; x++) {
        if(track[y][x] == 'S') {
            cur.px = x;
            cur.py = y;
            break;
        }
    }

    solve(cur.val);

    int sol_len = 0;
    while(track[cur.py][cur.px] != '*') {
        cache_t::iterator it = cache.find(cur.val);
        if(it == cache.end() || it->second.nmoves >= MOVES_INF) {
            std::cerr << "Failed to solve at p=" << cur.px << "," << cur.py << " v=" << cur.vx << "," << cur.vy << std::endl;
            return true;
        }

        int dvx = it->second.dvx;
        int dvy = it->second.dvy;
        cur.vx += dvx;
        cur.vy += dvy;
        cur.px += cur.vx;
        cur.py += cur.vy;
        std::cout << dvx << " " << dvy << std::endl;
        sol_len++;
    }

    //std::cerr << "Score: " << ((float)sol_len) / target << std::endl;

    return true;
}

int main() {
    /* benchmarking: */
    //while(solve_one())
    //    ;

    /* regular running */
    solve_one();
    std::string line;
    while(std::cin) std::getline(std::cin, line);

    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 ( 15, 0) in 5 turns.
  8    19 x 13       18   0.27778   Racer reached goal at ( 1, 11) in 5 turns.
  9    60 x 10      107   0.14953   Racer reached goal at ( 2, 6) in 16 turns.
 10    31 x 31      106   0.25472   Racer reached goal at ( 28, 0) in 27 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.26860   Racer reached goal at ( 3, 42) in 65 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 ( 52, 26) in 8 turns.
 20    50 x 50      200   0.05500   Racer reached goal at ( 47, 46) in 11 turns.
-------------------------------------------------------------------------------------
TOTAL SCORE:              5.40009

新しいテストケース


1
まあ、このようなことはほとんど期待されていました。パズルには、動的プログラミングを実行不可能にするほど十分な状態がありません。入力する場合、解決するためにより高度な検索戦略を必要とする地図を提出する必要があります。
user2357112は、2014

レーサーはテストケースでどのように機能しますか?
user2357112は、2014

0.14(14移動)
nneonneo

その時間はかかっていますか、それとも移動/目標ですか?移動/ターゲットの場合、時間的にどのように機能しますか?
user2357112は、Monicaを14年

1
サイクル防止コードにバグを見つけたと思います。状態Sから検索が到達する各状態について、最適なパスはSに戻ることはできないと想定しています。最適なパス Sに戻る場合、状態は最適なパス上にないように見えるかもしれません(ループを削除してパスを短くするだけです)、その状態に対して結果が高すぎるかどうかは気にしません。ただし、最適なパスが状態AとBをこの順序で通過し、Bがまだスタックにある間に検索が最初にAを検出した場合、Aの結果はループ防止によってポイズニングされます。
user2357112は、2014

6

Python 2、決定論的、最適

これが私のレーサーです。ベンチマークではテストしていませんが(インストールするRubyのバージョンとインストーラーについてはまだ問題があります)、すべてを最適に制限時間内に解決するはずです。実行するコマンドはpython whateveryoucallthefile.pyです。-sコントローラーフラグが必要です。

# Breadth-first search.
# Future directions: bidirectional search and/or A*.

import operator
import time

acceleration_options = [(dvx, dvy) for dvx in [-1, 0, 1] for dvy in [-1, 0, 1]]

class ImpossibleRaceError(Exception): pass

def read_input(line_source=raw_input):
    # We don't use the target.
    target = int(line_source())

    width, height = map(int, line_source().split())
    input_grid = [line_source() for _ in xrange(height)]

    start = None
    for i in xrange(height):
        for j in xrange(width):
            if input_grid[i][j] == 'S':
                start = i, j
                break
        if start is not None:
            break

    walls = [[cell == '#' for cell in row] for row in input_grid]
    goals = [[cell == '*' for cell in row] for row in input_grid]

    return start, walls, goals

def default_bfs_stop_threshold(walls, goals):
    num_not_wall = sum(sum(map(operator.not_, row)) for row in walls)
    num_goals = sum(sum(row) for row in goals)
    return num_goals * num_not_wall

def bfs(start, walls, goals, stop_threshold=None):
    if stop_threshold is None:
        stop_threshold = default_bfs_stop_threshold(walls, goals)

    # State representation is (x, y, vx, vy)
    x, y = start
    initial_state = (x, y, 0, 0)
    frontier = {initial_state}
    # Visited set is tracked by a map from each state to the last move taken
    # before reaching that state.
    visited = {initial_state: None}

    while len(frontier) < stop_threshold:
        if not frontier:
            raise ImpossibleRaceError

        new_frontier = set()
        for x, y, vx, vy in frontier:
            for dvx, dvy in acceleration_options:
                new_vx, new_vy = vx+dvx, vy+dvy
                new_x, new_y = x+new_vx, y+new_vy
                new_state = (new_x, new_y, new_vx, new_vy)

                if not (0 <= new_x < len(walls) and 0 <= new_y < len(walls[0])):
                    continue
                if walls[new_x][new_y]:
                    continue
                if new_state in visited:
                    continue

                new_frontier.add(new_state)
                visited[new_state] = dvx, dvy

                if goals[new_x][new_y]:
                    return construct_path_from_bfs(new_state, visited)
        frontier = new_frontier

def construct_path_from_bfs(goal_state, best_moves):
    reversed_path = []
    current_state = goal_state
    while best_moves[current_state] is not None:
        move = best_moves[current_state]
        reversed_path.append(move)

        x, y, vx, vy = current_state
        dvx, dvy = move
        old_x, old_y = x-vx, y-vy # not old_vx or old_vy
        old_vx, old_vy = vx-dvx, vy-dvy
        current_state = (old_x, old_y, old_vx, old_vy)
    return reversed_path[::-1]

def main():
    t = time.time()

    start, walls, goals = read_input()
    path = bfs(start, walls, goals, float('inf'))
    for dvx, dvy in path:
        # I wrote the whole program with x pointing down and y pointing right.
        # Whoops. Gotta flip things for the output.
        print dvy, dvx

if __name__ == '__main__':
    main()

nneonneoのレーサーを調べた後(C ++コンパイラも持っていないため、実際にはテストしていません)、ゴールがどれだけ近くても、どんなに短くても、状態空間のほぼ網羅的な検索を実行しているようです。パスはすでに見つかりました。また、時間ルールは、長く複雑なソリューションでマップを構築することは、長く退屈な時間制限を必要とすることを発見しました。したがって、地図の送信は非常に簡単です。

新しいテストケース

(GitHubは長い行を表示できません。トラックはです*S.......[and so on].....


追加提出:Python 2、双方向検索

これは、約2か月前に、幅優先の送信を最適化しようとしたときに作成したアプローチです。当時存在していたテストケースについては改善されなかったので、私はそれを提出しませんでしたが、黒井の新しいマップについては、メモリキャップの下でかろうじて絞るだけのようです。私はまだ黒井のソルバーがこれを打ち負かすことを期待していますが、私はそれがどのように持続するかに興味があります。

# Bidirectional search.
# Future directions: A*.

import operator
import time

acceleration_options = [(dvx, dvy) for dvx in [-1, 0, 1] for dvy in [-1, 0, 1]]

class ImpossibleRaceError(Exception): pass

def read_input(line_source=raw_input):
    # We don't use the target.
    target = int(line_source())

    width, height = map(int, line_source().split())
    input_grid = [line_source() for _ in xrange(height)]

    start = None
    for i in xrange(height):
        for j in xrange(width):
            if input_grid[i][j] == 'S':
                start = i, j
                break
        if start is not None:
            break

    walls = [[cell == '#' for cell in row] for row in input_grid]
    goals = [[cell == '*' for cell in row] for row in input_grid]

    return start, walls, goals

def bfs_to_bidi_threshold(walls, goals):
    num_not_wall = sum(sum(map(operator.not_, row)) for row in walls)
    num_goals = sum(sum(row) for row in goals)
    return num_goals * (num_not_wall - num_goals)

class GridBasedGoalContainer(object):
    '''Supports testing whether a state is a goal state with `in`.

    Does not perform bounds checking.'''
    def __init__(self, goal_grid):
        self.goal_grid = goal_grid
    def __contains__(self, state):
        x, y, vx, vy = state
        return self.goal_grid[x][y]

def forward_step(state, acceleration):
    x, y, vx, vy = state
    dvx, dvy = acceleration

    new_vx, new_vy = vx+dvx, vy+dvy
    new_x, new_y = x+new_vx, y+new_vy

    return (new_x, new_y, new_vx, new_vy)

def backward_step(state, acceleration):
    x, y, vx, vy = state
    dvx, dvy = acceleration

    old_x, old_y = x-vx, y-vy
    old_vx, old_vy = vx-dvx, vy-dvy

    return (old_x, old_y, old_vx, old_vy)

def bfs(start, walls, goals):
    x, y = start
    initial_state = (x, y, 0, 0)
    initial_frontier = {initial_state}
    visited = {initial_state: None}

    goal_state, frontier, visited = general_bfs(
        frontier=initial_frontier,
        visited=visited,
        walls=walls,
        goalcontainer=GridBasedGoalContainer(goals),
        stop_threshold=float('inf'),
        step_function=forward_step
    )

    return construct_path_from_bfs(goal_state, visited)

def general_bfs(
        frontier,
        visited,
        walls,
        goalcontainer,
        stop_threshold,
        step_function):

    while len(frontier) <= stop_threshold:
        if not frontier:
            raise ImpossibleRaceError

        new_frontier = set()
        for state in frontier:
            for accel in acceleration_options:
                new_state = new_x, new_y, new_vx, new_vy = \
                        step_function(state, accel)

                if not (0 <= new_x < len(walls) and 0 <= new_y < len(walls[0])):
                    continue
                if walls[new_x][new_y]:
                    continue
                if new_state in visited:
                    continue

                new_frontier.add(new_state)
                visited[new_state] = accel

                if new_state in goalcontainer:
                    return new_state, frontier, visited
        frontier = new_frontier
    return None, frontier, visited

def max_velocity_component(n):
    # It takes a distance of at least 0.5*v*(v+1) to achieve a velocity of
    # v in the x or y direction. That means the map has to be at least
    # 1 + 0.5*v*(v+1) rows or columns long to accomodate such a velocity.
    # Solving for v, we get a velocity cap as follows.
    return int((2*n-1.75)**0.5 - 0.5)

def solver(
        start,
        walls,
        goals,
        mode='bidi'):

    x, y = start
    initial_state = (x, y, 0, 0)
    initial_frontier = {initial_state}
    visited = {initial_state: None}
    if mode == 'bidi':
        stop_threshold = bfs_to_bidi_threshold(walls, goals)
    elif mode == 'bfs':
        stop_threshold = float('inf')
    else:
        raise ValueError('Unsupported search mode: {}'.format(mode))

    goal_state, frontier, visited = general_bfs(
        frontier=initial_frontier,
        visited=visited,
        walls=walls,
        goalcontainer=GridBasedGoalContainer(goals),
        stop_threshold=stop_threshold,
        step_function=forward_step
    )

    if goal_state is not None:
        return construct_path_from_bfs(goal_state, visited)

    # Switching to bidirectional search.

    not_walls_or_goals = []
    goal_list = []
    for x in xrange(len(walls)):
        for y in xrange(len(walls[0])):
            if not walls[x][y] and not goals[x][y]:
                not_walls_or_goals.append((x, y))
            if goals[x][y]:
                goal_list.append((x, y))
    max_vx = max_velocity_component(len(walls))
    max_vy = max_velocity_component(len(walls[0]))
    reverse_visited = {(goal_x, goal_y, goal_x-prev_x, goal_y-prev_y): None
                        for goal_x, goal_y in goal_list
                        for prev_x, prev_y in not_walls_or_goals
                        if abs(goal_x-prev_x) <= max_vx
                        and abs(goal_y - prev_y) <= max_vy}
    reverse_frontier = set(reverse_visited)
    while goal_state is None:
        goal_state, reverse_frontier, reverse_visited = general_bfs(
            frontier=reverse_frontier,
            visited=reverse_visited,
            walls=walls,
            goalcontainer=frontier,
            stop_threshold=len(frontier),
            step_function=backward_step
        )
        if goal_state is not None:
            break
        goal_state, frontier, visited = general_bfs(
            frontier=frontier,
            visited=visited,
            walls=walls,
            goalcontainer=reverse_frontier,
            stop_threshold=len(reverse_frontier),
            step_function=forward_step
        )
    forward_path = construct_path_from_bfs(goal_state, visited)
    backward_path = construct_path_from_bfs(goal_state,
                                            reverse_visited,
                                            step_function=forward_step)
    return forward_path + backward_path[::-1]

def construct_path_from_bfs(goal_state,
                            best_moves,
                            step_function=backward_step):
    reversed_path = []
    current_state = goal_state
    while best_moves[current_state] is not None:
        move = best_moves[current_state]
        reversed_path.append(move)
        current_state = step_function(current_state, move)
    return reversed_path[::-1]

def main():
    start, walls, goals = read_input()
    t = time.time()
    path = solver(start, walls, goals)
    for dvx, dvy in path:
        # I wrote the whole program with x pointing down and y pointing right.
        # Whoops. Gotta flip things for the output.
        print dvy, dvx

if __name__ == '__main__':
    main()

これは、時々ケース12に失敗し、13いけない知っている、なぜエラーメッセージがやや...無愛想なので
レイ

@Ray私もエラーメッセージを受け取りますが、とにかく結果は常に得られます。コントローラーは既に終了しているのに、レーサープロセスを強制終了しようとするように見えるので、むしろコントローラーに何かがあると思います。
マーティンエンダー14

@ m.buettner理由がわかったので、-sを追加するとOKになります。
レイ14

@Rayそうそう、私はそれをやっている。結果が既に存在するにもかかわらず、コントローラーがプロセスを強制終了しようとすると、トラック13と14でエラーが発生します。私はそれを調べるべきだと思いますが、得点には影響しないので、まだ気にしませんでした。
マーティンエンダー14

残念ながら、別のルールを追加する必要がありました。この課題では、メモリが時間よりも制限されているように見えるため、メモリ消費を制限するためにハードを設定する必要がありました。レーサーが1GBを超えるメモリを使用する実行は、制限時間を超えた場合と同じ結果になり中止されます。現在のトラックのセットでは、スコアはこの変更の影響を受けていません。(周りのタイブレーカーの制限に達すると思いますn = 400。)最適化を適用するかどうかをお知らせください。テストを再実行できます。
マーティンエンダー

3

Python 3:6.49643(最適、BFS)

古い20ケースのベンチマークファイルのスコアは5.35643でした。@nneonneoによる解決策は、5.4になったため最適ではありません。多分いくつかのバグ。

このソリューションでは、BFSを使用してグラフを検索します。各検索状態は(x、y、dx、dy)の形式です。次に、マップを使用して、州から距離にマップします。最悪の場合、時間と空間の複雑さはO(n ^ 2 m ^ 2)です。これは、速度が高すぎないか、レーサーがクラッシュするため、めったに起こりません。実際、22個すべてのテストケースを完了するには、マシン上で3秒かかりました。

from collections import namedtuple, deque
import itertools

Field = namedtuple('Map', 'n m grids')

class Grid:
    WALL = '#'
    EMPTY = '.'
    START = 'S'
    END = '*'

def read_nums():
    return list(map(int, input().split()))

def read_field():
    m, n = read_nums()
    return Field(n, m, [input() for i in range(n)])

def find_start_pos(field):
    return next((i, j)
        for i in range(field.n) for j in range(field.m)
        if field.grids[i][j] == Grid.START)

def can_go(field, i, j):
    return 0 <= i < field.n and 0 <= j < field.m and field.grids[i][j] != Grid.WALL

def trace_path(start, end, prev):
    if end == start:
        return
    end, step = prev[end]
    yield from trace_path(start, end, prev)
    yield step

def solve(max_turns, field, time):
    i0, j0 = find_start_pos(field)
    p0 = i0, j0, 0, 0
    prev = {}
    que = deque([p0])
    directions = list(itertools.product((-1, 0, 1), (-1, 0, 1)))

    while que:
        p = i, j, vi, vj = que.popleft()
        for dvi, dvj in directions:
            vi1, vj1 = vi + dvi, vj + dvj
            i1, j1 = i + vi1, j + vj1
            if not can_go(field, i1, j1):
                continue
            p1 = i1, j1, vi1, vj1
            if p1 in prev:
                continue
            que.append(p1)
            prev[p1] = p, (dvi, dvj)
            if field.grids[i1][j1] == Grid.END:
                return trace_path(p0, p1, prev)
    return []

def main():
    for dvy, dvx in solve(int(input()), read_field(), float(input())):
        print(dvx, dvy)

main()

# 結果

± % time ruby controller.rb benchmark.txt python ../mybfs.py                                                                                                                                                                             !9349
["benchmark.txt", "python", "../mybfs.py"]

Running 'python ../mybfs.py' against benchmark.txt

 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.
-------------------------------------------------------------------------------------
TOTAL SCORE:                 6.49643

ruby controller.rb benchmark.txt python ../mybfs.py  3.06s user 0.06s system 99% cpu 3.146 total

はい、user2357112のコメントによると、nneonneoのサイクル防止にはバグがあります。私の知る限り、速度が制限されO(√n)ているため、O(n³)正方形グリッドでの実装が可能になります(他と同じように思われます)。タイブレーカーを追加して、本日後半にuser2357112の提出物に対してスコアを付けます。
マーティンエンダー14

ところで、別のテストケースを追加する予定はありますか?
マーティンエンダー14

@ m.buettnerいいえ、このゲームには十分な理解がありません。したがって、私のテストケースは興味深いものではありません。
レイ14

残念ながら、別のルールを追加する必要がありました。この課題では、メモリが時間よりも制限されているように見えるため、メモリ消費を制限するためにハードを設定する必要がありました。レーサーが1GBを超えるメモリを使用している実行は、制限時間を超えた場合と同じ効果で中止されます。このルールを使用すると、サイズのタイブレーカーの制限を最初に超えるのはあなたの提出物でありn=270、これが他の2つの「最適な」提出物よりも遅れている理由です。そうは言っても、あなたの提出は3つの中で最も遅いので、とにかく大きいタイブレーカーで3番目だったでしょう。
マーティンエンダー

最適化を適用するかどうかをお知らせください。テストを再実行できます。
マーティンエンダー

1

RandomRacer、最大40.0(10回の実行の平均)

このボットがトラックを決して終わらせないというわけではありませんが、10回の試行に1回よりも確実に少ない回数です。(20〜30回のシミュレーションごとに、最悪ではないスコアを取得します。)

これは主に、ベースラインのケースとして機能し、レーサーの可能な(Ruby)実装を示すためのものです。

# Parse initial input
target = gets.to_i
size = gets.split.map(&:to_i)
track = []
size[1].times do
    track.push gets
end
time_budget = gets.to_f

# Find start position
start_y = track.find_index { |row| row['S'] }
start_x = track[start_y].index 'S'

position = [start_x, start_y]
velocity = [0, 0]

while true
    x = rand(3) - 1
    y = rand(3) - 1
    puts [x,y].join ' '
    $stdout.flush

    first_line = gets
    break if !first_line || first_line.chomp.empty?

    position = first_line.split.map(&:to_i)
    velocity = gets.split.map(&:to_i)
    time_budget = gets.to_f
end

で実行する

ruby controller.rb benchmark.txt ruby randomracer.rb

1

ランダムレーサー2.0、〜31

これは、投稿された最適なソルバーに勝るものではありませんが、ランダムレーサーのわずかな改善です。主な違いは、このレーサーは、移動する有効な場所を使い果たしない限り、壁のない場所にランダムに移動することのみを考慮し、そのターンにゴールに移動できる場合はそうすることです。また、同じ場所に留まるために移動できるものはありません(他に移動できない場合はありません(可能性は低いですが可能です)。

Javaで実装され、java8でコンパイルされますが、Java 6は問題ありません。コマンドラインパラメータはありません。階層のかなり良いクラスターファックがあるので、私はJavaを正しくやっていると思います。

import java.util.Scanner;
import java.util.Random;
import java.util.ArrayList;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

public class VectorRacing   {
    private static Scanner in = new Scanner(System.in);
    private static Random rand = new Random();
    private static Track track;
    private static Racer racer;
    private static int target;
    private static double time;
    public static void main(String[] args)  {
        init();
        main_loop();
    }
    private static void main_loop() {
        Scanner linescan;
        String line;
        int count = 0,
            x, y, u, v;

        while(!racer.lost() && !racer.won() && count < target)  {
            Direction d = racer.think();
            racer.move(d);
            count++;
            System.out.println(d);

            line = in.nextLine();
            if(line.equals("")) {
                break;
            }
            linescan = new Scanner(line);
            x = linescan.nextInt();
            y = linescan.nextInt();
            linescan = new Scanner(in.nextLine());
            u = linescan.nextInt();
            v = linescan.nextInt();
            time = Double.parseDouble(in.nextLine());

            assert x == racer.location.x;
            assert y == racer.location.y;
            assert u == racer.direction.x;
            assert v == racer.direction.y;
        }
    }
    private static void init()  {
        target = Integer.parseInt(in.nextLine());
        int width = in.nextInt();
        int height = Integer.parseInt(in.nextLine().trim());
        String[] ascii = new String[height];
        for(int i = 0; i < height; i++) {
            ascii[i] = in.nextLine();
        }
        time = Double.parseDouble(in.nextLine());
        track = new Track(width, height, ascii);
        for(int y = 0; y < ascii.length; y++)   {
            int x = ascii[y].indexOf("S");
            if( x != -1)    {
                racer = new RandomRacer(track, new Location(x, y));
                break;
            }
        }
    }

    public static class RandomRacer extends Racer   {
        public RandomRacer(Track t, Location l) {
            super(t, l);
        }
        public Direction think()    {
            ArrayList<Pair<Location, Direction> > possible = this.getLocationsCanMoveTo();
            if(possible.size() == 0)    {
                return Direction.NONE;
            }
            Pair<Location, Direction> ret = null;
            do  {
                ret = possible.get(rand.nextInt(possible.size()));
            }   while(possible.size() != 1 && ret.a.equals(this.location));
            return ret.b;
        }
    }

    // Base things
    public enum Direction   {
        NORTH("0 -1"), SOUTH("0 1"), EAST("1 0"), WEST("-1 0"), NONE("0 0"),
        NORTH_EAST("1 -1"), NORTH_WEST("-1 -1"), SOUTH_EAST("1 1"), SOUTH_WEST("-1 1");

        private final String d;
        private Direction(String d) {this.d = d;}
        public String toString()    {return d;}
    }
    public enum Cell    {
        WALL('#'), GOAL('*'), ROAD('.'), OUT_OF_BOUNDS('?');

        private final char c;
        private Cell(char c)    {this.c = c;}
        public String toString()    {return "" + c;}
    }

    public static class Track   {
        private Cell[][] track;
        private int target;
        private double time;
        public Track(int width, int height, String[] ascii) {
            this.track = new Cell[width][height];
            for(int y = 0; y < height; y++) {
                for(int x = 0; x < width; x++)  {
                    switch(ascii[y].charAt(x))  {
                        case '#':   this.track[x][y] = Cell.WALL; break;
                        case '*':   this.track[x][y] = Cell.GOAL; break;
                        case '.':
                        case 'S':   this.track[x][y] = Cell.ROAD; break;
                        default:    System.exit(-1);
                    }
                }
            }
        }
        public Cell atLocation(Location loc)    {
            if(loc.x < 0 || loc.x >= track.length || loc.y < 0 || loc.y >= track[0].length) return Cell.OUT_OF_BOUNDS;
            return track[loc.x][loc.y];
        }

        public String toString()    {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            PrintStream ps = new PrintStream(bos);
            for(int y = 0; y < track[0].length; y++)    {
                for(int x = 0; x < track.length; x++)   {
                    ps.append(track[x][y].toString());
                }
                ps.append('\n');
            }
            String ret = bos.toString();
            ps.close();
            return ret;
        }
    }

    public static abstract class Racer  {
        protected Velocity tdir;
        protected Location tloc;
        protected Track track;
        public Velocity direction;
        public Location location;

        public Racer(Track track, Location start)   {
            this.track = track;
            direction = new Velocity(0, 0);
            location = start;
        }
        public boolean canMove() throws GoHereDammitException {return canMove(Direction.NONE);}
        public boolean canMove(Direction d) throws GoHereDammitException    {
            tdir = new Velocity(direction);
            tloc = new Location(location);
            tdir.add(d);
            tloc.move(tdir);
            Cell at = track.atLocation(tloc);
            if(at == Cell.GOAL) {
                throw new GoHereDammitException();
            }
            return at == Cell.ROAD;
        }
        public ArrayList<Pair<Location, Direction> > getLocationsCanMoveTo()    {
            ArrayList<Pair<Location, Direction> > ret = new ArrayList<Pair<Location, Direction> >(9);
            for(Direction d: Direction.values())    {
                try {
                    if(this.canMove(d)) {
                        ret.add(new Pair<Location, Direction>(tloc, d));
                    }
                }   catch(GoHereDammitException e)  {
                    ret.clear();
                    ret.add(new Pair<Location, Direction>(tloc, d));
                    return ret;
                }
            }
            return ret;
        }
        public void move()  {move(Direction.NONE);}
        public void move(Direction d)   {
            direction.add(d);
            location.move(direction);
        }
        public boolean won()    {
            return track.atLocation(location) == Cell.GOAL;
        }
        public boolean lost()   {
            return track.atLocation(location) == Cell.WALL || track.atLocation(location) == Cell.OUT_OF_BOUNDS;
        }
        public String toString()    {
            return location + ", " + direction;
        }
        public abstract Direction think();

        public class GoHereDammitException extends Exception    {
            public GoHereDammitException()  {}
        }
    }

    public static class Location extends Point  {
        public Location(int x, int y)   {
            super(x, y);
        }
        public Location(Location l) {
            super(l);
        }
        public void move(Velocity d)    {
            this.x += d.x;
            this.y += d.y;
        }
    }

    public static class Velocity extends Point  {
        public Velocity(int x, int y)   {
            super(x, y);
        }
        public Velocity(Velocity v) {
            super(v);
        }
        public void add(Direction d)    {
            if(d == Direction.NONE) return;
            if(d == Direction.NORTH || d == Direction.NORTH_EAST || d == Direction.NORTH_WEST)  this.y--;
            if(d == Direction.SOUTH || d == Direction.SOUTH_EAST || d == Direction.SOUTH_WEST)  this.y++;
            if(d == Direction.EAST || d == Direction.NORTH_EAST || d == Direction.SOUTH_EAST)   this.x++;
            if(d == Direction.WEST || d == Direction.NORTH_WEST || d == Direction.SOUTH_WEST)   this.x--;
        }
    }

    public static class Point   {
        protected int x, y;
        protected Point(int x, int y)   {
            this.x = x;
            this.y = y;
        }
        protected Point(Point c)    {
            this.x = c.x;
            this.y = c.y;
        }
        public int getX()   {return x;}
        public int getY()   {return y;}
        public String toString()    {return "(" + x + ", " + y + ")";}
        public boolean equals(Point p)  {
            return this.x == p.x && this.y == p.y;
        }
    }

    public static class Pair<T, U>  {
        public T a;
        public U b;
        public Pair(T t, U u)   {
            a=t;b=u;
        }
    }
}

結果(私が見たベストケース)

Running 'java VectorRacing' against ruby-runner/benchmark.txt

 No.    Size     Target   Score     Details
-------------------------------------------------------------------------------------
  1    37 x 1        36   0.38889   Racer reached goal at ( 36, 0) in 14 turns.
  2    38 x 1        37   0.54054   Racer reached goal at ( 37, 0) in 20 turns.
  3    33 x 1        32   0.62500   Racer reached goal at ( 32, 0) in 20 turns.
  4    10 x 10       10   0.40000   Racer reached goal at ( 9, 8) in 4 turns.
  5     9 x 6         8   0.75000   Racer reached goal at ( 6, 2) in 6 turns.
  6    15 x 7        16   2.00000   Racer did not reach the goal within 16 turns.
  7    17 x 8        16   2.00000   Racer hit a wall at position ( 8, 2).
  8    19 x 13       18   0.44444   Racer reached goal at ( 16, 2) in 8 turns.
  9    60 x 10      107   0.65421   Racer reached goal at ( 0, 6) in 70 turns.
 10    31 x 31      106   2.00000   Racer hit a wall at position ( 25, 9).
 11    31 x 31      106   2.00000   Racer hit a wall at position ( 8, 1).
 12    50 x 20       50   2.00000   Racer hit a wall at position ( 27, 14).
 13   100 x 100    2600   2.00000   Racer went out of bounds at position ( 105, 99).
 14    79 x 63      242   2.00000   Racer went out of bounds at position (-2, 26).
 15    26 x 1        25   0.32000   Racer reached goal at ( 25, 0) in 8 turns.
 16    17 x 1        19   2.00000   Racer went out of bounds at position (-2, 0).
 17    50 x 1        55   2.00000   Racer went out of bounds at position ( 53, 0).
 18    10 x 7        23   2.00000   Racer went out of bounds at position ( 10, 2).
 19    55 x 55       45   0.33333   Racer reached goal at ( 4, 49) in 15 turns.
 20    50 x 50      200   2.00000   Racer hit a wall at position ( 14, 7).
-------------------------------------------------------------------------------------
TOTAL SCORE:             26.45641

はい、.class何らかの理由で(コントローラーのあるディレクトリではなく)ファイルがあるディレクトリから実行する必要がありましたが、実行しました。テストケースを追加することにした場合は、(コメント付きで)pingを送信して、ベンチマークに追加できるようにします。スコアは10回の実行で約33回でした(リーダーボードを参照)が、これはベンチマークに追加される新しいテストトラックごとに変わる可能性があります。
マーティンエンダー

ああ、他のディレクトリからも実行できるようになりました。コマンドラインでJavaに慣れていない場合:java -cp path/to/class/file VectorRacing
Martin Ender

ああ、そう、私はたくさんのクラスを作りました(正確には13です)。クラスディレクトリからスクリプトを常に実行していたため、実際にはテストしませんでした。私はテストケースを作るかもしれませんが、最初はランダムではないレーサーを作ろうと思います。
pseudonym117

はい。その場合は、別の回答として追加してください。(そして、それぞれに1つのテストケースを追加してください。)
マーティンエンダー

残念ながら、別のルールを追加する必要がありました。この課題では、メモリが時間よりも制限されているように見えるため、メモリ消費を制限するためにハードを設定する必要がありました。レーサーが1GBを超えるメモリを使用する実行は、制限時間を超えた場合と同じ結果になり中止されます。現在のトラックのセットでは、スコアはこの変更の影響を受けていません(おそらく影響を受けません)。
マーティンエンダー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.