До ветеринарного! - Гран-прі векторних гонок


39

Користувач CarpetPython опублікував нову проблему, яка ставить значно більшу увагу на евристичні рішення, завдяки збільшенню місця для пошуку. Я особисто вважаю, що виклик набагато приємніший, ніж мій, тому подумайте про те, щоб спробувати це!

Векторні гонки - захоплююча гра, яку можна пограти з ручкою та аркушем паперу квадратних ліній. Ви малюєте на папері довільну бігову доріжку, визначаєте старт і кінець, а потім керуєте автомобілем розміру в точці по черзі. Добирайтеся до кінця якомога швидше, але будьте обережні, щоб не опинитися в стіні!

Доріжка

  • Карта - це двовимірна сітка, де кожна комірка має цілі координати.
  • Ви переходите по комірок сітки.
  • Кожна комірка сітки є або частиною доріжки, або є стіною.
  • Рівно одна комірка треку є початковою координатою.
  • Принаймні одна комірка треку позначена як мета. Посадка на будь-яку з них завершує гонку. Осередки з декількома цілями не обов'язково з'єднуються.

Кермо автомобіля

Ваш автомобіль починається з заданої координати та з вектором швидкості (0, 0). На кожному кроці ви можете налаштувати кожну складову швидкості ±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

Де x, y, u, vвсе 0 на основі цілих чисел. (x,y)- це ваше поточне положення та (u,v)ваша поточна швидкість. Зауважте, що x+uта / або y+vможе бути поза межами.

timeце все, що залишилося від вашого часового бюджету, в секундах. Сміливо ігноруйте це. Це стосується лише тих учасників, які дійсно хочуть взяти участь у встановленому терміні.

Коли гра закінчена (оскільки ви приземлилися в стіну, вийшли за межі, перевищили targetповороти, не вистачало часу або досягли мети), ви отримаєте порожній рядок.

Вихідні дані

На кожну чергу випишіть у stdout :

Δu Δv

де Δuі Δvкожного з них один з -1, 0, 1. Це буде додано (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 , тому ви можете використовувати його для будь-якого виводу налагодження, який може знадобитися під час розробки вашого бота. Будь-ласка, видаліть / коментуйте будь-який такий вихід у наданому вами коді.

Вашому боту може знадобитися півсекунди, щоб відповісти за кожен поворот. Для поворотів, які займають більше часу, у вас буде бюджет часу (на трек) target/2секунд. Кожен раз, коли поворот триває більше півсекунди, додатковий час буде відніматися з вашого бюджету часу. Коли ваш часовий бюджет досягне нуля, поточна гонка буде перервана.

Нове. З практичних причин я повинен встановити ліміт пам’яті (оскільки пам'ять здається більш обмеженою, ніж час для розумних розмірів доріжок). Тому мені доведеться перервати будь-який тестовий пробіг, коли гонщик використовує більше 1 ГБ пам'яті, виміряну процесором Explorer як приватні байти .

Оцінка балів

Є орієнтир з 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

Репозиторій також містить два класи Point2Dта Track. Якщо ваше повідомлення написано на Ruby, не соромтесь використовувати їх для зручності.

Перемикачі командного рядка

Ви можете додати параметри командного рядка -v, -s, -tперед ім'ям файлу в бенчмарка. Якщо ви хочете використовувати кілька перемикачів, ви також можете зробити, наприклад, -vs. Це те, що вони роблять:

-v (багатослівний): Використовуйте це, щоб отримати трохи більше налагодження виводу з контролера.

-s (мовчить): Якщо ви віддаєте перевагу самостійно стежити за своєю позицією та швидкістю і не піклуватися про бюджет часу, ви можете вимкнути ці три рядки виходу на кожну чергу (надіслані до подання) із цим прапором.

-t(треки): Дозволяє вибирати окремі доріжки для тестування. Наприклад, -t "1,2,5..8,15"слід перевірити лише треки 1, 2, 5, 6, 7, 8 і 15. Дякую Вентеро за цю функцію та розбір варіантів.

Ваше подання

Підсумовуючи, будь ласка, включіть у відповідь:

  • Ваш рахунок.
  • Якщо ви використовуєте випадковість, будь ласка, вкажіть це, щоб я міг оцінити ваш бал за кілька циклів.
  • Код для подання.
  • Розташування безкоштовного компілятора чи перекладача на вашу обрану мову, що працює на машині Windows 8.
  • Інструкції з компіляції, якщо це необхідно.
  • Рядок командного рядка Windows для запуску подання.
  • Незалежно від того, чи потрібно подати -sпрапор, чи ні.
  • (необов'язково) Нова вирішувана доріжка, яка буде додана до еталону. Я визначу розумний targetдля вашої доріжки вручну. Коли трек буде доданий до еталону, я відредагую його з вашої відповіді. Я залишаю за собою право попросити вас про інший трек (про всяк випадок, якщо ви додасте непропорційно велику доріжку, включіть у трек непристойне мистецтво ASCII тощо). Коли я додаю тестовий випадок до набору тестів, я заміню доріжку у вашій відповіді на посилання на доріжку у файлі еталону, щоб зменшити безлад у цій публікації.

Як бачите, я тестую всі матеріали на машині Windows 8. Якщо абсолютно немає способу запустити ваше представлення в Windows, я також можу спробувати UMM Ubuntu. Це буде значно повільніше, тому, якщо ви хочете збільшити максимальний термін, переконайтеся, що ваша програма працює в Windows.

Нехай найкращий водій вийде векторальним!

Але я хочу грати!

Якщо ви хочете спробувати гру самостійно, щоб краще відчути її, є така реалізація . Правила, які там використовуються, є дещо складнішими, але це досить схоже, щоб бути корисним.

Таблиця лідерів

Останнє оновлення: 01.09.2014, 21:29 UTC
Доріжки в орієнтирі: 25
Розміри автоматичних вимикачів: 290, 440

  1. 6.86688 - Курой Неко
  2. 8.73108 - user2357112 - 2-е подання
  3. 9.86627 - nneonneo
  4. 10.66109 - користувач2357112 - 1-е подання
  5. 12.49643 - Рей
  6. 40.0759 - псевдонім117 (імовірнісний)

Детальні результати тесту . (Оцінки за вірогідними поданнями визначаються окремо.)

Відповіді:


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 ++ має хитрість, щоб змусити вас стрибати через обручі просто для переміщення відповідника. Однак, ви можете ввімкнути це, щоб створити відносно швидкий і ефективний пам'ять код.

Хешинг

Тут головним є надання хорошої хеш-таблиці для вузлів. Це, безумовно, домінуючий чинник швидкості виконання.
Дві реалізації unordered_set(GNU та Microsoft) дали 30% різниці швидкостей виконання (на користь GNU, так!).

Різниця насправді не дивна, що з вантажівками коду, прихованого позаду unordered_set.

З цікавості я зробив кілька статистичних даних щодо остаточного стану хеш-таблиці.
Обидва алгоритми мають майже однакове співвідношення між ковшем та елементами, але перерозподіл змінюється:
для вимикача 290x290 зв'язання GNU отримує в середньому 1,5 елемента за не порожнє відро, а Microsoft становить 5,8 (!).

Схоже, що моя функція хешування не дуже добре рандомізована Microsoft ... Цікаво, чи хлопці в Редмонд дійсно орієнтували свій STL, чи, можливо, мій випадок використання просто сприяє реалізації GNU ...

Звичайно, моя хеш-функція ніде не є оптимальною. Я міг би використовувати звичайне ціле змішування на основі декількох змін / мулів, але хеш-ефективна функція потребує часу для обчислення.

Здається, кількість запитів хеш-таблиць дуже велика порівняно з кількістю вставок. Наприклад, у вимикачі 290x290 краватки у вас є близько 3,6 млн вставок для 22,7 мільйонів запитів.
У цьому контексті субоптимальний, але швидкий хешинг дає кращі результати.

Розподіл пам'яті

Забезпечення ефективного розподільника пам'яті займає друге місце. Це покращило показники приблизно на 30%. Чи варто того, щоб доданий код для лайна є дискусійним :).

Поточна версія використовує від 40 до 55 байт на вузол.
Для функціональних даних потрібен 24 байти для вузла (4 координати та 2 вказівника).
З-за божевільного тестового випадку 100 000 рядків координати потрібно зберігати в 4-байтних словах, інакше ви можете отримати 8 байт, використовуючи шорти (з максимальним значенням координати 32767). Решта байтів в основному споживаються хеш-таблицею без упорядкованого набору. Це означає, що обробка даних фактично витрачає трохи більше, ніж "корисна" корисна навантаження.

І переможець ...

На моєму комп'ютері під Win7 вимикач краватки (випадок 23, 290х290) вирішується найгіршою версією (тобто, складеною Microsoft) приблизно за 2,2 секунди, споживання пам'яті становить близько 185 Мб.
Для порівняння, поточний лідер (код python від user2357112) займає трохи більше 30 секунд і витрачає близько 780 Мб.

Питання контролера

Я не зовсім впевнений, що мені вдасться зашифрувати в Ruby, щоб врятувати своє життя.
Однак я помітив і зламав дві проблеми з коду контролера:

1) читання карти в track.rb

Із встановленим рубіном 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 Мбіт!).

Що має менше половини поверхні вимикача 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";
?>

Ерф ... Мій алокатор безболісної кістки просто трохи босовий. Я додаю необхідне лайно, щоб потім працювати з g ++. Вибач за це.

Гаразд, це виправлено. Версія g ++ навіть реально працює на 30% швидше. Тепер він видає статистику на stderr. Сміливо коментуйте це (з останніх рядків джерела). Вибачте ще раз за помилку.

Гаразд, це працює зараз, і я відтворив ваш бал. Чорт швидко! :) Я додам ваш тестовий випадок до еталону, але я зміню цільовий показник 400, так як це відповідає тому, як я визначив усі інші цілі (крім вимикача з краваткою). Я оновлю головну публікацію, як тільки перекину всі інші подання.
Мартін Ендер

Оновили результати. Не було потреби в автоматичному вимикачі, оскільки всі інші матеріали перевищують обмеження на вашій тестовій доріжці. Вітаю! :)
Мартін Ендер

Спасибі. Насправді цей виклик дав мені привід заглибитись у ці хеш-таблиці STL. Хоча я ненавиджу кишки С ++, я не можу не вбити, але мене вб'є цікавість. Мяу! :).

10

C ++, 5.4 (детермінований, оптимальний)

Рішення для динамічного програмування. Доведено оптимально. Дуже швидко: розв’язує всі 20 тестів за 0,2 секунди. Має бути особливо швидким на 64-бітних машинах. Припускаємо, що дошка займає менше 32 000 місць у кожному напрямку (що, сподіваємось, має бути правдою).

Цей гонщик трохи незвичний. Він обчислює оптимальний шлях на стартовій лінії, а потім виконує обчислений шлях миттєво. Він ігнорує контроль часу і припускає, що він може вчасно закінчити крок оптимізації (що має бути справедливим для будь-якого досить сучасного обладнання). На надмірно великих картах є невелика ймовірність, що гонщик може бути за замовчуванням. Якщо ви можете переконати його в segfault, ви отримаєте точку браузера, і я виправлю це для використання явного циклу.

Компілювати з g++ -O3. Може знадобитися C ++ 11 (для <unordered_map>). Для запуску просто запустіть скомпільований виконуваний файл (не підтримуються жодні прапори чи параметри; весь вхід зроблено на 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;
}

Результати

 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 підтримує Моніку

Як ваш гонщик працює у вашому тестовому випадку?
user2357112 підтримує Моніку

0,14 (14 ходів)
nneonneo

Це зайнятий час або рухається / націляється? Якщо це переміщення / ціль, як це виконується за часом?
user2357112 підтримує Моніку

1
Я думаю, що я знайшов помилку в коді запобігання циклу. Передбачається , що для кожного стану пошуку досягає зі стану S, оптимальний шлях не може бути , щоб повернутися до S. Може здатися , що якщо оптимальний шлях робить повернення до S, то держава не може бути на оптимальному шляху (так як ми могли б просто зніміть цикл, на якому він увімкнений, і отримайте коротший шлях), і тому нам не байдуже, чи отримаємо ми занадто високий результат для цього стану. Однак якщо оптимальний шлях проходить через стани A і B у такому порядку, але пошук спочатку виявляє A, поки B все ще знаходиться на стеці, то результати A отруюються запобіганням циклу.
user2357112 підтримує Моніку

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, двосторонній пошук

Це підхід, про який я написав близько двох місяців тому, коли намагався оптимізувати свою першу широту. Для тестових випадків, які існували на той час, він не запропонував поліпшення, тому я не подав його, але для нової карти Куроя, здається, він ледве стискається під кришкою пам'яті. Я все ще сподіваюся, що вирішувач Курої це переможе, але мені цікаво, як воно тримається.

# 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 Я також отримую повідомлення про помилки, але я завжди отримую результат для них. Я думаю, що це може бути щось у моєму контролері, тому що схоже, що контролер намагається вбити гонщиковий процес, хоча це вже закінчено.
Мартін Ендер

@ m.buettner Я знайшов причину, add -s, тоді все буде гаразд.
Рей

@Ray О так, я це роблю. Я все ще отримую помилку на треках 13 і 14, коли контролер намагається вбити процес, хоча результат вже є. Я думаю, що я повинен розглянути це, але це не впливає на оцінку, тому я ще не турбував.
Мартін Ендер

На жаль, мені довелося додати ще одне правило. Пам'ять, здається, в цьому виклику обмежує час, ніж час, тому мені довелося встановити жорсткий обмеження на споживання пам'яті. Будь-який пробіг, в якому ваш гонщик використовує більше 1 ГБ пам'яті, буде перервано з тим же ефектом, що і перевищити межу часу. Для поточного набору треків ця зміна не вплинула на ваш рахунок. (Я думаю, ви досягнете цієї межі на вимикачах n = 400.) Будь ласка, повідомте мене, якщо ви застосовуєте якісь оптимізації, щоб я міг повторно випробувати тести.
Мартін Ендер

3

Python 3: 6.49643 (Optimal, BFS)

Для старого файлу з 20 еталонними критеріями він отримав оцінку 5,35643. Розв’язання @nneonneo не є оптимальним, оскільки отримало 5,4. Можливо, деякі помилки.

Це рішення використовує BFS для пошуку в Графіці, кожен стан пошуку має форму (x, y, dx, dy). Тоді я використовую карту для картування зі станів на відстані. У гіршому випадку, час і просторова складність - це O (n ^ 2 m ^ 2). Це буде рідко, оскільки швидкість не буде занадто високою або гонщик зазнає аварії. Насправді на моїй машині коштувало 3 секунди, щоб виконати всі 22 тести.

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³)на квадратних сітках (так само, як і інші, я думаю). Я додаю вимикач краватки, щоб оцінити ваше подання порівняно з користувачем2357112 пізніше сьогодні.
Мартін Ендер

Btw, ви плануєте додати ще один тестовий випадок?
Мартін Ендер

@ m.buettner Ні, я не маю достатньо хорошого розуміння для цієї гри. Тож моя перевірка не буде цікавою.
Рей

На жаль, мені довелося додати ще одне правило. Пам'ять, здається, в цьому виклику обмежує час, ніж час, тому мені довелося встановити жорсткий обмеження на споживання пам'яті. Будь-який пробіг, в якому ваш гонщик використовує більше 1 ГБ пам'яті, буде перервано з тим же ефектом, що і перевищити межу часу. За цим правилом, ваше повідомлення першим перевищило цю межу розміру вимикача за розміром n=270, тому ви тепер відстаєте від двох інших "оптимальних" подань. Коли це було сказано, ваше подання є також найповільнішим із трьох, так що все-таки було б третім, тільки з більшим вимикачем.
Мартін Ендер

Будь ласка, повідомте мене, якщо ви застосовуєте якісь оптимізації, щоб я міг повторно перевірити.
Мартін Ендер

1

RandomRacer, ~ 40,0 (в середньому за 10 циклів)

Справа не в тому, що цей бот ніколи не закінчує трек, але, безумовно, набагато рідше, ніж раз у 10 спроб. (Я отримую бал не найгіршого випадку кожні 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файл є чомусь (замість каталогу, де знаходиться контролер). Пінг мені (з коментарем), якщо ви вирішили додати тестовий зразок, тож я можу додати його до еталону. Ваш бал становив близько 33 за 10 пробіжок (див. Таблицю лідерів), але це може змінитися з кожним новим тестовим треком, який додається до еталону.
Мартін Ендер

Ах, він також запустився з іншого каталогу. Для тих, хто не знайомий з Java в командному рядку:java -cp path/to/class/file VectorRacing
Мартін Ендер

Ага, так, я склав тонну занять (якщо бути точним). Я завжди запускав ваш сценарій із каталогу моїх класів, тому насправді цього не перевіряв. Я можу зробити тестовий випадок, але я думаю, що спробую зробити гонщика, який не є випадковим першим.
псевдонім117

Звичайно. Якщо це зробити, будь ласка, додайте це як окрему відповідь. (І сміливо додайте по одному тестовому випадку з кожним із них.)
Мартін Ендер,

На жаль, мені довелося додати ще одне правило. Пам'ять, здається, в цьому виклику обмежує час, ніж час, тому мені довелося встановити жорсткий обмеження на споживання пам'яті. Будь-який пробіг, в якому ваш гонщик використовує більше 1 ГБ пам'яті, буде перервано з тим же ефектом, що і перевищити межу часу. Для поточного набору треків ця зміна не вплинула (і, швидше за все, не буде).
Мартін Ендер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.