Untuk Vectory! - Grand Prix Balap Vektor

39

Pengguna CarpetPython memposting pandangan baru tentang masalah ini yang menempatkan fokus yang jauh lebih besar pada solusi heuristik, karena peningkatan ruang pencarian. Saya pribadi berpikir bahwa tantangan jauh lebih baik daripada tantangan saya, jadi pertimbangkan untuk mencoba yang itu!

Balap vektor adalah permainan adiktif yang bisa dimainkan dengan pena dan selembar kertas yang dikuadratkan. Anda menggambar arena pacuan kuda sewenang-wenang di atas kertas, menentukan awal dan akhir dan kemudian Anda mengarahkan mobil berukuran titik Anda dengan cara berbasis giliran. Selesaikan secepat mungkin, tetapi berhati-hatilah agar tidak berakhir di dinding!

Jalanan

  • Peta adalah kisi dua dimensi, di mana setiap sel memiliki koordinat bilangan bulat.
  • Anda memindahkan sel-sel kisi.
  • Setiap sel kisi adalah bagian dari lintasan atau dinding.
  • Satu sel track yang tepat adalah koordinat awal.
  • Setidaknya satu sel trek ditetapkan sebagai tujuan. Mendarat pada salah satu dari ini melengkapi balapan. Banyak sel tujuan tidak selalu terhubung.

Mengemudikan Mobil

Mobil Anda mulai dari koordinat yang diberikan dan dengan vektor kecepatan (0, 0). Di setiap belokan, Anda dapat menyesuaikan setiap komponen kecepatan dengan ±1atau membiarkannya apa adanya. Kemudian, vektor kecepatan yang dihasilkan ditambahkan ke posisi mobil Anda.

Sebuah gambar dapat membantu! Lingkaran merah adalah posisi terakhir Anda. Lingkaran biru adalah posisi Anda saat ini. Kecepatan Anda adalah vektor dari lingkaran merah ke biru. Pada gilirannya ini, tergantung pada bagaimana Anda menyesuaikan kecepatan Anda, Anda dapat pindah ke lingkaran hijau mana saja.

                                    masukkan deskripsi gambar di sini

Jika Anda mendarat di dinding, Anda langsung kalah.

Tugas Anda

Anda dapat menebaknya: tulis sebuah program yang, diberi lintasan pacuan kuda sebagai input, mengarahkan mobil ke salah satu sel gawang dalam beberapa putaran sebanyak mungkin. Solusi Anda harus dapat menangani trek sewenang-wenang dengan cukup baik dan tidak secara khusus dioptimalkan terhadap kasus uji yang disediakan.

Memasukkan

Ketika program Anda dipanggil, baca dari stdin :

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

targetadalah jumlah belokan maksimum yang dapat Anda ambil untuk menyelesaikan trek, dan timemerupakan total waktu anggaran Anda untuk trek, dalam detik (tidak harus bilangan bulat). Lihat di bawah untuk detail tentang waktu.

Untuk trek yang dibatasi baris baru, karakter berikut digunakan:

  • # - dinding
  • S- yang start
  • *- sebuah tujuan
  • . - semua sel trek lainnya (yaitu jalan)

Semua sel di luar n x mgrid yang disediakan tersirat sebagai dinding.

Asal koordinat berada di sudut kiri atas.

Ini adalah contoh sederhana:

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

Menggunakan pengindeksan berbasis 0, koordinat awal adalah (0,4).

Setelah setiap gerakan, Anda akan menerima masukan lebih lanjut:

x y
u v
time

Dimana x, y, u, vsemua bilangan bulat 0 berbasis. (x,y)adalah posisi (u,v)Anda saat ini dan kecepatan Anda saat ini. Catat itu x+udan / atau y+vbisa di luar batas.

timeapa pun yang tersisa dari anggaran waktu Anda, dalam hitungan detik. Jangan ragu untuk mengabaikan ini. Ini hanya untuk peserta yang benar-benar ingin membawa implementasi mereka ke batas waktu.

Saat permainan berakhir (karena Anda mendarat di dinding, keluar dari batas, melewati targetputaran, kehabisan waktu, atau mencapai tujuan), Anda akan menerima garis kosong.

Keluaran

Untuk setiap belokan, tulis ke stdout :

Δu Δv

mana Δudan Δvsetiap adalah salah satu -1, 0, 1. Ini akan ditambahkan ke (u,v)untuk menentukan posisi baru Anda. Hanya untuk memperjelas, petunjuknya adalah sebagai berikut

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

Solusi optimal untuk contoh di atas adalah

1 0
1 -1
1 0

Perhatikan bahwa pengontrol tidak melekat pada stderr , jadi Anda bebas menggunakannya untuk segala jenis hasil debug yang mungkin Anda perlukan saat mengembangkan bot Anda. Harap hapus / komentari output apa pun dalam kode yang Anda kirimkan.

Bot Anda mungkin membutuhkan setengah detik untuk merespons di setiap belokan. Untuk belokan yang membutuhkan waktu lebih lama, Anda akan memiliki anggaran waktu (per track) target/2detik. Setiap kali belokan membutuhkan waktu lebih dari setengah detik, waktu tambahan akan dikurangkan dari anggaran waktu Anda. Ketika anggaran waktu Anda mencapai nol, balapan saat ini akan dibatalkan.

Baru: Untuk alasan praktis, saya harus menetapkan batas memori (karena memori tampaknya lebih membatasi daripada waktu untuk ukuran trek yang masuk akal). Oleh karena itu, saya harus membatalkan semua uji coba di mana pembalap menggunakan lebih dari 1GB memori yang diukur oleh Process Explorer sebagai Private Bytes .

Mencetak gol

Ada patokan 20 lagu. Untuk setiap lagu:

  • Jika Anda menyelesaikan trek, skor Anda adalah jumlah gerakan yang Anda butuhkan untuk mencapai sel tujuan yang dibagitarget .
  • Jika Anda kehabisan waktu / memori atau tidak mencapai tujuan sebelum targetbelokan telah berlalu atau Anda mendarat di dinding / di luar batas setiap saat, skor Anda adalah 2.
  • Jika program Anda tidak deterministik, skor Anda adalah rata-rata lebih dari 10 kali berjalan di trek itu (sebutkan dalam jawaban Anda)

Skor keseluruhan Anda adalah jumlah skor trek individu. Menang skor terendah!

Selain itu, setiap peserta dapat (dan sangat disarankan untuk) memberikan track benchmark tambahan , yang akan ditambahkan ke daftar resmi. Jawaban sebelumnya akan dievaluasi kembali termasuk trek baru ini. Ini untuk memastikan bahwa tidak ada solusi yang dirancang terlalu dekat dengan kasus uji yang ada dan untuk memperhitungkan kasus tepi yang menarik yang mungkin saya lewatkan (tetapi Anda mungkin melihat).

Tie tie

Sekarang sudah ada solusi optimal, ini mungkin akan menjadi faktor utama untuk skor peserta.

Jika ada dasi (karena beberapa jawaban menyelesaikan semua trek secara optimal atau tidak), saya akan menambahkan tambahan (lebih besar) kasus uji untuk memutus ikatan. Untuk menghindari bias manusia saat membuat tie-breaker, ini akan dihasilkan secara tetap:

  • Aku akan menambah panjang sisi noleh 10dibandingkan dengan lagu terakhir yang dihasilkan dengan cara ini. (Saya dapat melewati ukuran jika mereka tidak memutuskan dasi.)
  • Dasarnya adalah vektor-grafis ini
  • Ini akan dirasterisasi pada resolusi yang diinginkan menggunakan potongan Mathematica ini .
  • Awal adalah di sudut kiri atas. Khususnya, itu akan menjadi sel paling kiri dari baris paling atas dari ujung trek.
  • Tujuannya ada di sudut kanan bawah. Khususnya, itu akan menjadi sel paling kanan dari baris paling bawah dari ujung trek.
  • Surat targetwasiat 4*n.

Lagu terakhir dari tolok ukur awal sudah dibuat seperti ini, dengan n = 50.

Pengendali

Program yang menguji pengiriman ditulis dalam Ruby dan dapat ditemukan di GitHub bersama dengan file benchmark yang akan saya gunakan. Ada juga bot contoh yang disebut randomracer.rbdi sana, yang hanya mengambil gerakan acak. Anda dapat menggunakan struktur dasarnya sebagai titik awal untuk bot Anda untuk melihat bagaimana komunikasi bekerja.

Anda dapat menjalankan bot Anda sendiri terhadap file trek pilihan Anda sebagai berikut:

ruby controller.rb track_file_name command to run your racer

misalnya

ruby controller.rb benchmark.txt ruby randomracer.rb

Repositori juga mengandung dua kelas Point2Ddan Track. Jika kiriman Anda ditulis dalam Ruby, silakan menggunakannya kembali untuk kenyamanan Anda.

Sakelar baris perintah

Anda dapat menambahkan switch baris perintah -v, -s, -tsebelum nama file patokan. Jika Anda ingin menggunakan beberapa sakelar, Anda juga bisa, misalnya -vs,. Inilah yang mereka lakukan:

-v (verbose): Gunakan ini untuk menghasilkan lebih sedikit keluaran debug dari pengontrol.

-s (diam): Jika Anda lebih suka melacak posisi dan kecepatan Anda sendiri dan tidak peduli dengan anggaran waktu, Anda dapat mematikan tiga jalur output tersebut setiap belokan (dikirim ke kiriman Anda) dengan bendera ini.

-t(trek): Memungkinkan Anda memilih masing-masing trek untuk diuji. Misalnya -t "1,2,5..8,15"akan menguji trek 1, 2, 5, 6, 7, 8 dan 15 saja. Terima kasih banyak kepada Ventero untuk fitur ini dan parser opsi.

Kiriman Anda

Singkatnya, harap sertakan yang berikut dalam jawaban Anda:

  • Nilai Anda.
  • Jika Anda menggunakan keacakan, sebutkan ini, jadi saya bisa menilai skor Anda selama beberapa kali.
  • Kode untuk kiriman Anda.
  • Lokasi kompiler atau juru bahasa gratis untuk bahasa pilihan Anda yang berjalan pada mesin Windows 8.
  • Instruksi kompilasi jika perlu.
  • String baris perintah Windows untuk menjalankan kiriman Anda.
  • Apakah kiriman Anda memerlukan -sbenderanya atau tidak.
  • (opsional) Lagu baru yang dapat dipecahkan yang akan ditambahkan ke tolok ukur. Saya akan menentukan wajar targetuntuk trek Anda secara manual. Saat trek ditambahkan ke tolok ukur, saya akan mengeditnya dari jawaban Anda. Saya berhak meminta trek berbeda dari Anda (kalau-kalau Anda menambahkan trek besar yang tidak proporsional, sertakan seni ASCII cabul di trek, dll.). Ketika saya menambahkan test case ke set benchmark, saya akan mengganti trek di jawaban Anda dengan tautan ke track di file benchmark untuk mengurangi kekacauan dalam posting ini.

Seperti yang Anda lihat, saya akan menguji semua pengiriman pada mesin Windows 8. Jika sama sekali tidak ada cara agar kiriman Anda berjalan di Windows, saya juga dapat mencoba di VM Ubuntu. Ini akan jauh lebih lambat, jadi jika Anda ingin memaksimalkan batas waktu, pastikan program Anda berjalan pada Windows.

Semoga pembalap terbaik muncul vectorious!

Tapi saya ingin bermain!

Jika Anda ingin mencoba permainan sendiri untuk mendapatkan rasa yang lebih baik untuk itu, ada implementasi ini . Aturan yang digunakan di sana sedikit lebih canggih, tetapi cukup mirip untuk berguna, saya pikir.

Papan peringkat

Terakhir diperbarui: 01/09/2014, 21:29 UTC
Track dalam benchmark: 25
Ukuran tie-breaker: 290, 440

  1. 6.86688 - Kuroi Neko
  2. 8.73108 - user2357112 - pengiriman ke-2
  3. 9,86627 - nneonneo
  4. 10.66109 - user2357112 - pengiriman pertama
  5. 12.49643 - Ray
  6. 40.0759 - pseudonym117 (probabilistic)

Hasil tes terperinci . (Nilai untuk pengiriman probabilistik telah ditentukan secara terpisah.)

Martin Ender
sumber

Jawaban:

5

C ++ 11 - 6.66109

Namun implementasi pencarian luas pertama lainnya, hanya dioptimalkan.

Itu harus dijalankan dengan opsi -s .
Inputnya tidak dibersihkan sama sekali, jadi trek yang salah mungkin mengubahnya menjadi labu.

Saya mengujinya dengan Microsoft Visual C ++ 2013, rilis build dengan flag default / O2 (mengoptimalkan kecepatan).
TIDAK membangun OK dengan g ++ dan Microsoft IDE.
Pengalokasi memori barebone saya adalah omong kosong, jadi jangan berharap itu bekerja dengan implementasi STL lainnya 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;
}

Hasil

 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

Pertunjukan

Bahasa C ++ jelek itu memiliki kemampuan untuk membuat Anda melompat melewati lingkaran hanya untuk memindahkan batang korek api. Namun, Anda dapat membuatnya menjadi kode yang relatif cepat dan hemat memori.

Hashing

Di sini kuncinya adalah untuk menyediakan tabel hash yang baik untuk node. Sejauh ini faktor dominan untuk kecepatan eksekusi.
Dua implementasi unordered_set(GNU dan Microsoft) menghasilkan perbedaan kecepatan eksekusi 30% (mendukung GNU, yay!).

Perbedaannya tidak terlalu mengejutkan, ada apa dengan truk-truk kode tersembunyi di belakang unordered_set.

Karena penasaran, saya melakukan beberapa statistik pada keadaan akhir dari tabel hash.
Kedua algoritma berakhir dengan rasio bucket / elemen yang hampir sama, tetapi partisi ulang bervariasi:
untuk pemutus ikatan 290x290, GNU mendapatkan rata-rata 1,5 elemen per ember yang tidak kosong, sementara Microsoft berada pada 5,8 (!).

Sepertinya fungsi hashing saya tidak diacak dengan baik oleh Microsoft ... Saya ingin tahu apakah orang-orang di Redmond benar-benar membandingkan STL mereka, atau mungkin case use saya hanya mendukung implementasi GNU ...

Tentu, fungsi hashing saya jauh dari optimal. Saya bisa menggunakan bilangan bulat integer yang biasa berdasarkan beberapa shift / muls tetapi fungsi hash efisien membutuhkan waktu untuk menghitung.

Tampaknya jumlah kueri tabel hash sangat tinggi dibandingkan dengan jumlah penyisipan. Misalnya, dalam pemutus dasi 290x290, Anda memiliki sekitar 3,6 juta penyisipan untuk 22,7 juta kueri.
Dalam konteks ini, hashing suboptimal tapi cepat menghasilkan kinerja yang lebih baik.

Alokasi memori

Menyediakan pengalokasi memori yang efisien menempati urutan kedua. Ini meningkatkan kinerja sekitar 30%. Apakah itu sepadan dengan kode omong kosong yang ditambahkan masih bisa diperdebatkan :).

Versi saat ini menggunakan antara 40 dan 55 byte per node.
Data fungsional memerlukan 24 byte untuk sebuah node (4 koordinat dan 2 pointer).
Karena case test gila 100.000 baris, koordinat harus disimpan dalam 4 byte kata, jika tidak Anda bisa mendapatkan 8 byte dengan menggunakan celana pendek (dengan nilai koordinat maksimum 32767). Byte yang tersisa sebagian besar dikonsumsi oleh tabel hash set unordered. Itu berarti penanganan data sebenarnya mengkonsumsi sedikit lebih banyak daripada muatan "berguna".

Dan pemenangnya adalah...

Pada PC saya di bawah Win7, tie breaker (case 23, 290x290) diselesaikan oleh versi terburuk (yaitu Microsoft dikompilasi) dalam waktu sekitar 2,2 detik, dengan konsumsi memori sekitar 185 Mb.
Sebagai perbandingan, pemimpin saat ini (kode python oleh user2357112) membutuhkan waktu lebih dari 30 detik dan mengkonsumsi sekitar 780 Mb.

Masalah pengontrol

Saya tidak yakin saya bisa kode di Ruby untuk menyelamatkan hidup saya.
Namun, saya melihat dan meretas dua masalah dari kode controller:

1) membaca peta di track.rb

Dengan ruby ​​1.9.3 terinstal, pembaca trek akan bersuara tentang shift.to_itidak tersedia string.lines.
Setelah lama mengarungi dokumentasi Ruby online saya menyerah pada string dan menggunakan array perantara sebagai gantinya, seperti itu (tepat di awal file):

def initialize(string)
    @track = Array.new;
    string.lines.each do |line|
        @track.push (line.chomp)
    end

2) membunuh hantu di controller.rb

Seperti yang telah dicatat oleh poster lain, controller terkadang mencoba mematikan proses yang sudah keluar. Untuk menghindari output kesalahan yang tidak sopan ini, saya hanya menangani pengecualian itu, seperti itu (sekitar baris 134):

if silent
    begin # ;!;
        Process.kill('KILL', racer.pid)
    rescue Exception => e
    end

Kasus cobaan

Untuk mengalahkan pendekatan brute force dari pemecah BFS, jalur terburuk adalah kebalikan dari peta 100.000 sel: area yang sepenuhnya bebas dengan tujuan sejauh mungkin dari awal.

Dalam contoh ini, peta 100x400 dengan sasaran di sudut kiri atas dan awal di kanan bawah.

Peta ini memiliki solusi dalam 28 putaran, tetapi pemecah BFS akan menjelajahi jutaan negara untuk menemukannya (tambang menghitung 10.022.658 negara yang dikunjungi, membutuhkan waktu sekitar 12 detik dan memuncak pada 600 Mb!).

Dengan kurang dari setengah permukaan tie breaker 290x290, diperlukan sekitar 3 kali lebih banyak kunjungan node. Di sisi lain, pemecah berbasis heuristik / A * harus mengalahkannya dengan mudah.

30
100 400
*...................................................................................................
....................................................................................................
                          < 400 lines in all >
....................................................................................................
....................................................................................................
...................................................................................................S

Bonus: versi PHP yang setara (tapi agak kurang efisien)

Inilah yang saya mulai dengan, sebelum lambatnya bahasa bawaan meyakinkan saya untuk menggunakan C ++.
Tabel hash internal PHP sepertinya tidak seefisien Python, setidaknya dalam kasus khusus ini :).

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

sumber
erf ... Pengalokasi barebone saya terlalu sedikit barebone. Saya akan menambahkan omong kosong yang diperlukan untuk membuatnya bekerja dengan g ++, lalu. Maaf soal itu.
Oke, itu sudah diperbaiki. Versi g ++ bahkan bekerja sekitar 30% lebih cepat. Sekarang menampilkan beberapa statistik di stderr. Jangan ragu untuk berkomentar (dari baris terakhir sumber). Maaf lagi untuk kesalahan.
Baiklah, itu berfungsi sekarang dan saya mereproduksi skor Anda. Ini sangat cepat! :) Saya akan menambahkan test case Anda ke benchmark, tapi saya akan mengubah target 400, karena itu sejalan dengan cara saya menentukan semua target lainnya (kecuali tie breaker). Saya akan memperbarui posting utama setelah saya mengulang semua kiriman lainnya.
Martin Ender
Diperbarui hasilnya. Tidak perlu pemutus dasi, karena semua kiriman lainnya melebihi batas memori di trek uji Anda. Selamat atas itu! :)
Martin Ender
Terima kasih. Sebenarnya tantangan ini memberi saya kesempatan untuk menggali tabel hash STL ini. Meskipun aku membenci nyali C ++, aku tidak bisa tidak terbunuh oleh keingintahuanku. Meong! :)
10

C ++, 5.4 (deterministik, optimal)

Solusi pemrograman dinamis. Terbukti optimal. Sangat cepat: menyelesaikan semua 20 testcases dalam 0,2s. Seharusnya sangat cepat pada mesin 64-bit. Asumsikan dewan kurang dari 32.000 tempat di setiap arah (yang semoga benar).

Pembalap ini sedikit tidak biasa. Itu menghitung jalur optimal di garis awal, dan kemudian mengeksekusi jalur dihitung secara instan. Ini mengabaikan kontrol waktu dan menganggap itu dapat menyelesaikan langkah optimasi tepat waktu (yang seharusnya berlaku untuk perangkat keras yang cukup modern). Pada peta yang terlalu besar, ada kemungkinan kecil bahwa pembalap dapat melakukan segmentasi. Jika Anda dapat meyakinkan itu untuk segfault, Anda mendapatkan titik brownies, dan saya akan memperbaikinya untuk menggunakan loop eksplisit.

Kompilasi dengan g++ -O3. Mungkin membutuhkan C ++ 11 (untuk <unordered_map>). Untuk menjalankan, jalankan executable yang dikompilasi (tidak ada flag atau opsi yang didukung; semua input diambil pada stdin).

#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;
}

Hasil

 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

Testcase Baru

nneonneo
sumber
1
Ya, sesuatu seperti ini sudah cukup banyak diharapkan. Tidak ada cukup keadaan dalam teka-teki untuk membuat pemrograman dinamis tidak mungkin dilakukan. Jika saya masuk, saya perlu mengirimkan peta yang membutuhkan strategi pencarian yang lebih canggih untuk dipecahkan.
user2357112 mendukung Monica
Bagaimana performa pembalap Anda pada test case Anda?
user2357112 mendukung Monica
0,14 (14 gerakan)
nneonneo
Apakah itu waktu yang diambil atau bergerak / target? Jika bergerak / target, bagaimana kinerjanya dalam hal waktu?
user2357112 mendukung Monica
1
Saya pikir saya telah menemukan bug dalam kode pencegahan siklus. Ini mengasumsikan bahwa untuk setiap negara mencapai pencarian dari keadaan S, jalur optimal tidak bisa kembali ke S. Ini mungkin tampak bahwa jika jalur optimal tidak kembali ke S, maka negara tidak bisa berada di jalur yang optimal (karena kita bisa hapus saja loop yang ada dan dapatkan jalur yang lebih pendek), jadi kami tidak peduli apakah kami mendapatkan hasil yang terlalu tinggi untuk kondisi itu. Namun, jika jalur optimal melewati negara bagian A dan B dalam urutan itu, tetapi pencarian pertama kali menemukan A saat B masih di tumpukan, maka hasil A diracuni oleh pencegahan loop.
user2357112 mendukung Monica
6

Python 2 , deterministik, optimal

Ini pembalap saya. Saya belum mengujinya pada benchmark (masih bingung tentang apa versi dan installer Ruby untuk menginstal), tetapi harus menyelesaikan semuanya secara optimal dan di bawah batas waktu. Perintah untuk menjalankannya adalah python whateveryoucallthefile.py. Membutuhkan -sflag controller.

# 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()

Setelah memeriksa pembalap nneonneo (tetapi tidak benar-benar mengujinya, karena saya juga tidak memiliki kompiler C ++), saya telah menemukan bahwa tampaknya melakukan pencarian yang hampir lengkap dari ruang keadaan, tidak peduli seberapa dekat tujuan atau seberapa pendek sebuah jalan telah ditemukan. Saya juga menemukan bahwa aturan waktu berarti membangun peta dengan solusi yang panjang dan rumit membutuhkan batas waktu yang lama dan membosankan. Dengan demikian, pengiriman peta saya cukup sederhana:

Testcase Baru

(GitHub tidak dapat menampilkan garis panjang. Treknya adalah *S.......[and so on].....)


Pengajuan tambahan: Python 2, pencarian dua arah

Ini adalah pendekatan yang saya tulis sekitar dua bulan lalu, ketika mencoba untuk mengoptimalkan pengiriman pertama saya yang luas. Untuk kasus uji yang ada pada saat itu, itu tidak menawarkan perbaikan, jadi saya tidak mengirimkannya, tetapi untuk peta baru kuroi, tampaknya hanya sedikit terjepit di bawah tutup memori. Saya masih mengharapkan pemecah kuroi untuk mengalahkan ini, tapi saya tertarik pada bagaimana ia bertahan.

# 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()
user2357112 mendukung Monica
sumber
Ini terkadang gagal pada kasus 12 dan 13. Tidak tahu mengapa karena pesan kesalahan agak ... tidak ramah
Ray
@ Ray aku juga mendapatkan pesan kesalahan, tapi aku selalu mendapatkan hasilnya untuk itu. Saya pikir itu mungkin lebih seperti sesuatu di controller saya, karena sepertinya controller mencoba untuk membunuh proses pembalap meskipun sudah selesai.
Martin Ender
@ m.buettner Saya menemukan alasannya, tambahkan -s maka tidak masalah.
Ray
@ Ray Oh ya, saya melakukan itu. Saya masih mendapatkan kesalahan pada trek 13 dan 14 ketika controller mencoba mematikan proses meskipun hasilnya sudah ada. Saya kira saya harus melihat ke dalam itu, tetapi tidak mempengaruhi skor jadi saya belum repot-repot.
Martin Ender
Sayangnya, saya harus menambahkan aturan lain. Memori tampaknya lebih membatasi daripada waktu dalam tantangan ini, jadi saya harus menetapkan batas konsumsi memori yang sulit. Setiap menjalankan di mana pembalap Anda menggunakan lebih dari 1GB memori akan dibatalkan dengan efek yang sama seperti melebihi batas waktu. Untuk rangkaian lagu saat ini, skor Anda belum terpengaruh oleh perubahan ini. (Saya pikir Anda akan mencapai batas pada tie-breaker sekitar n = 400.) Tolong beri tahu saya jika Anda menerapkan optimasi, sehingga saya dapat menjalankan kembali tes.
Martin Ender
3

Python 3: 6.49643 (Optimal, BFS)

Untuk file benchmark 20 kasus lama, mendapat skor 5,35643. Solusi oleh @nneonneo tidak optimal karena mendapat 5.4. Beberapa bug mungkin.

Solusi ini menggunakan BFS untuk mencari Grafik, setiap keadaan pencarian dalam bentuk (x, y, dx, dy). Lalu saya menggunakan peta untuk memetakan dari negara ke jarak. Dalam kasus terburuk, kompleksitas waktu dan ruangnya adalah O (n ^ 2 m ^ 2). Ini jarang terjadi karena kecepatan tidak akan terlalu tinggi atau pembalap akan crash. Sebenarnya, harganya 3 detik di mesin saya untuk menyelesaikan semua 22 testcases.

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()

# Hasil

± % 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
sinar
sumber
Ya, menurut komentar pengguna2357112 ada bug dalam pencegahan siklus nneonneo. Sejauh yang saya tahu, kecepatan dibatasi oleh O(√n)yang akan membuat implementasi Anda O(n³)pada kotak persegi (sama seperti yang lain, saya kira). Saya akan menambahkan tie breaker untuk menilai kiriman Anda dibandingkan dengan pengguna2357112 hari ini.
Martin Ender
Btw, apakah Anda berencana untuk menambah test case lain?
Martin Ender
@ m.buettner Tidak, saya tidak memiliki pemahaman yang cukup baik untuk game ini. Jadi testcase saya tidak akan menarik.
Ray
Sayangnya, saya harus menambahkan aturan lain. Memori tampaknya lebih membatasi daripada waktu dalam tantangan ini, jadi saya harus menetapkan batas konsumsi memori yang sulit. Setiap menjalankan di mana pembalap Anda menggunakan lebih dari 1GB memori akan dibatalkan dengan efek yang sama seperti melebihi batas waktu. Dengan aturan ini, kiriman Anda adalah yang pertama yang melampaui batas ukuran tie-breaker n=270, itulah sebabnya Anda sekarang berada di belakang dua kiriman "optimal" lainnya. Yang sedang berkata, kiriman Anda juga paling lambat dari ketiganya, jadi akan menjadi yang ketiga, hanya dengan tie-breaker yang lebih besar.
Martin Ender
Harap beri tahu saya jika Anda menerapkan optimasi apa pun, sehingga saya dapat menjalankan kembali tes.
Martin Ender
1

RandomRacer, ~ 40.0 (rata-rata lebih dari 10 berjalan)

Bukan berarti bot ini tidak pernah menyelesaikan trek, tapi jelas jauh lebih jarang dari sekali dalam 10 upaya. (Saya mendapatkan skor kasus terburuk setiap 20 hingga 30 simulasi.)

Ini sebagian besar untuk bertindak sebagai kasus dasar dan untuk menunjukkan kemungkinan implementasi (Ruby) untuk pembalap:

# 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

Jalankan dengan

ruby controller.rb benchmark.txt ruby randomracer.rb
Martin Ender
sumber
1

Pembalap Acak 2.0, ~ 31

Yah ini tidak akan mengalahkan pemecah optimal yang diposting, tetapi ini adalah sedikit peningkatan pada pembalap acak. Perbedaan utama adalah bahwa pembalap ini hanya akan mempertimbangkan secara acak pergi di mana tidak ada dinding, kecuali itu kehabisan tempat yang valid untuk bergerak, dan jika itu dapat pindah ke tujuan yang berbelok, itu akan. Itu juga tidak akan bergerak untuk tetap di tempat yang sama, kecuali tidak ada langkah lain yang tersedia (tidak mungkin, tetapi mungkin).

Diimplementasikan di Jawa, dikompilasi dengan java8, tetapi Java 6 harus baik-baik saja. Tidak ada parameter baris perintah. Ada hirarki clusterfuck yang cukup bagus, jadi saya pikir saya melakukan java dengan benar.

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;
        }
    }
}

Hasil (Kasus terbaik yang pernah saya lihat)

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
pseudonim117
sumber
Ya, saya menjalankannya, meskipun saya harus menjalankannya dari direktori di mana .classfile itu untuk beberapa alasan (bukan direktori di mana controller berada). Ping saya (dengan komentar) jika Anda memutuskan untuk menambahkan testcase, jadi saya dapat menambahkannya ke patokan. Skor Anda adalah sekitar 33 lebih dari 10 kali jalan (lihat papan peringkat), tetapi ini mungkin berubah dengan setiap jalur tes baru yang ditambahkan ke tolok ukur.
Martin Ender
Ah berhasil menjalankannya dari direktori lain juga. Bagi mereka yang tidak terbiasa dengan Jawa pada baris perintah:java -cp path/to/class/file VectorRacing
Martin Ender
Ah, ya saya membuat banyak kelas (tepatnya 13). Saya selalu menjalankan skrip Anda dari direktori kelas saya, jadi saya tidak benar-benar mengujinya. Saya mungkin membuat test case, tapi saya pikir saya akan mencoba membuat pembalap yang tidak acak terlebih dahulu.
pseudonym117
Yakin. Jika ya, tambahkan sebagai jawaban terpisah. (Dan jangan ragu untuk menambahkan satu test case dengan masing-masing dari mereka.)
Martin Ender
Sayangnya, saya harus menambahkan aturan lain. Memori tampaknya lebih membatasi daripada waktu dalam tantangan ini, jadi saya harus menetapkan batas konsumsi memori yang sulit. Setiap menjalankan di mana pembalap Anda menggunakan lebih dari 1GB memori akan dibatalkan dengan efek yang sama seperti melebihi batas waktu. Untuk rangkaian lagu saat ini, skor Anda belum terpengaruh oleh perubahan ini (dan kemungkinan tidak akan pernah terjadi).
Martin Ender