Вектори! - Гран-при Vector Racing

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 ГБ памяти, что измеряется Process 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. Большое спасибо Ventero за эту функцию и парсер опций.

Ваше представление

В заключение, пожалуйста, включите в свой ответ следующее:

  • Твой счет.
  • Если вы используете случайность, укажите это, чтобы я мог усреднить ваш результат за несколько прогонов.
  • Код для вашего представления.
  • Расположение бесплатного компилятора или интерпретатора для вашего языка, выбранного на компьютере с Windows 8.
  • Инструкция по компиляции при необходимости.
  • Строка командной строки Windows для запуска вашего представления.
  • Требует ли ваше представление -sфлаг или нет.
  • (опционально) Новый, разрешимый трек, который будет добавлен в тест. Я определю разумный targetдля вашего трека вручную. Когда трек будет добавлен в тест, я отредактирую его из вашего ответа. Я оставляю за собой право попросить вас о другой дорожке (на случай, если вы добавите непропорционально большую дорожку, включите в нее непристойную графику ASCII и т. Д.). Когда я добавлю тестовый набор в набор тестов, я заменим трек в вашем ответе ссылкой на трек в файле тестов, чтобы уменьшить беспорядок в этом посте.

Как вы можете видеть, я буду тестировать все материалы на компьютере с Windows 8. Если нет абсолютно никакой возможности запустить ваше представление в Windows, я также могу попробовать виртуальную машину Ubuntu. Это будет значительно медленнее, поэтому, если вы хотите максимально увеличить ограничение времени, убедитесь, что ваша программа работает в Windows.

Пусть лучший водитель появится в векторе!

Но я хочу играть!

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

Leaderboard

Последнее обновление: 01.09.2014, 21:29 UTC
Дорожки в тесте: 25
Размеры стяжки: 290, 440

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

Подробные результаты теста . (Баллы за вероятностные представления были определены отдельно.)

Мартин Эндер
источник

Ответы:

5

С ++ 11 - 6,66109

Еще одна широкая реализация поиска, только оптимизированная.

Он должен быть запущен с опцией -s .
Его вход вообще не очищен, поэтому неправильные дорожки могут превратить его в тыкву.

Я протестировал его с Microsoft Visual C ++ 2013, выпуск сборки с флагом по умолчанию / O2 (оптимизация по скорости).
ДЕЙСТВИТЕЛЬНО строит нормально с g ++ и Microsoft IDE.
Мой распределитель памяти barebone - это дерьмо, поэтому не ожидайте, что он будет работать с другими реализациями 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, 290x290) решается самой плохой версией (то есть скомпилированной Microsoft) примерно за 2,2 секунды с потреблением памяти около 185 Мб.
Для сравнения, текущий лидер (код Python от user2357112) занимает чуть более 30 секунд и потребляет около 780 Мб.

Проблемы с контроллером

Я не совсем уверен, что смогу написать код на Ruby, чтобы спасти мою жизнь.
Тем не менее, я обнаружил и взломал две проблемы из кода контроллера:

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

С установленным ruby ​​1.9.3 трекер считал бы, shift.to_iчто он недоступен string.lines.
После долгих размышлений по онлайн-документации по Ruby я отказался от строк и вместо этого использовал промежуточный массив, вот так (прямо в начале файла):

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

2) убивать призраков в controller.rb

Как уже отмечали другие авторы, контроллер иногда пытается завершить процессы, которые уже вышли. Чтобы избежать этих позорных сообщений об ошибках, я просто обработал исключение, вот так (около строки 134):

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

Прецедент

Чтобы победить подход грубой силы решателей BFS, худший путь - это противоположность карте из 100 000 ячеек: полностью свободная область с целью как можно дальше от начала.

В этом примере карта 100x400 с целью в левом верхнем углу и началом в правом нижнем углу.

Эта карта имеет решение за 28 ходов, но решатель BFS исследует миллионы штатов, чтобы найти его (моя насчитала 10,022,658 посещенных штатов, заняла около 12 секунд и достигла пика в 600 Мб!).

При меньшей половине поверхности связующего выключателя 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";
?>

источник
э-э-э ... Мой распределитель barebone-файлов слишком чересчур. Затем я добавлю необходимую хрень, чтобы она работала с g ++. Прости за это.
ОК, это исправлено. Версия g ++ даже работает на 30% быстрее. Теперь выводит некоторую статистику по stderr. Не стесняйтесь комментировать (из последних строк источника). Снова извините за ошибку.
Хорошо, теперь это работает, и я воспроизвел ваш счет. Это чертовски быстро! :) Я добавлю ваш тестовый пример в тест, но я изменю цель на 400, так как это соответствует тому, как я определил все другие цели (кроме прерывателя связей). Я обновлю основной пост, как только перейду на все остальные материалы.
Мартин Эндер
Обновлены результаты. В прерывании связей не было необходимости, потому что все остальные представления превышают лимит памяти на вашем тестовом треке. Поздравляю с этим! :)
Мартин Эндер
Спасибо. На самом деле этот вызов дал мне повод покопаться в этих хеш-таблицах STL. Хотя я ненавижу смелость C ++, я не могу не быть убитым своим любопытством. Мяу! :).
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

Новый тест-кейс

nneonneo
источник
1
Ну, как-то так и ожидалось. В загадке недостаточно состояния, чтобы сделать динамическое программирование невозможным. Если я вхожу, мне нужно будет представить карту, для решения которой требуются более сложные стратегии поиска.
user2357112 поддерживает Monica
Как ваш гонщик работает на вашем тестовом примере?
user2357112 поддерживает Monica
0,14 (14 ходов)
nneonneo
Это время занято или движется / цель? Если это движение / цель, как это работает с точки зрения времени?
user2357112 поддерживает Monica
1
Я думаю, что я нашел ошибку в коде предотвращения цикла. Предполагается, что для каждого состояния, в котором поиск достигает состояния S, оптимальный путь не может быть возвращением к S. Может показаться, что если оптимальный путь действительно возвращается к S, то состояние не может быть на оптимальном пути (поскольку мы могли бы просто удалите цикл, который включен, и получите более короткий путь), и поэтому нам все равно, получим ли мы слишком высокий результат для этого состояния. Однако, если оптимальный путь проходит через состояния A и B в указанном порядке, но поиск сначала находит A, пока B все еще находится в стеке, то результаты A отравляются предотвращением цикла.
user2357112 поддерживает Monica
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()
user2357112 поддерживает Monica
источник
Это иногда терпит неудачу в случаях 12 и 13. Не знаю почему, так как сообщения об ошибках несколько ... недружелюбны
Рэй
@Ray Я тоже получаю сообщения об ошибках, но я всегда получаю результат для них. Я думаю, что это может быть что-то в моем контроллере, потому что похоже, что контроллер пытается убить процесс гонщика, хотя он уже завершен.
Мартин Эндер
@ m.buettner Я нашел причину, добавить -s, тогда все будет хорошо.
Рэй
@ Ray О, да, я делаю это. Я все еще получаю сообщение об ошибке на треках 13 и 14, когда контроллер пытается завершить процесс, хотя результат уже есть. Думаю, мне стоит разобраться с этим, но это не влияет на счет, поэтому я пока не беспокоился.
Мартин Эндер
К сожалению, мне пришлось добавить еще одно правило. В этой задаче память кажется более ограниченной, чем время, поэтому мне пришлось жестко ограничить потребление памяти. Любой заезд, в котором ваш гонщик использует более 1 ГБ памяти, будет прерван с тем же эффектом, что и превышение ограничения по времени. Для текущего набора треков это изменение не повлияло на ваш счет. (Я думаю, что вы достигнете этого предела для тай-брейков вокруг n = 400.) Пожалуйста, дайте мне знать, если вы примените какие-либо оптимизации, чтобы я мог повторно запустить тесты.
Мартин Эндер
3

Python 3: 6.49643 (оптимальный, 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³)на квадратных сетках (как и другие, я полагаю). Я добавлю прерыватель связи, чтобы оценить вашу работу по сравнению с user2357112 позже сегодня.
Мартин Эндер
Кстати, вы планируете добавить еще один тест?
Мартин Эндер
@ 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
pseudonym117
источник
Да, я запустил его, хотя мне пришлось запускать его из каталога, где .classпо какой-то причине находится файл (а не из каталога, где находится контроллер). Пинг мне (с комментарием), если вы решите добавить тестовый пример, чтобы я мог добавить его в тест. Ваша оценка была около 33 за 10 пробежек (см. Таблицу лидеров), но это может измениться с каждым новым тестовым треком, который добавляется в тест.
Мартин Эндер
Ах, запустил его из другого каталога тоже. Для тех, кто не знаком с Java в командной строке:java -cp path/to/class/file VectorRacing
Мартин Эндер
Ах, да, я сделал кучу уроков (13, если быть точным). Я всегда запускал твой сценарий из своего каталога классов, поэтому я не проверял это. Я могу сделать тестовый пример, но я думаю, что я постараюсь сделать гонщика, который не является случайным первым.
псевдоним117
Конечно. Если вы это сделаете, пожалуйста, добавьте его в качестве отдельного ответа. (И не стесняйтесь добавлять один тестовый пример с каждым из них.)
Мартин Эндер
К сожалению, мне пришлось добавить еще одно правило. В этой задаче память кажется более ограниченной, чем время, поэтому мне пришлось жестко ограничить потребление памяти. Любой заезд, в котором ваш гонщик использует более 1 ГБ памяти, будет прерван с тем же эффектом, что и превышение ограничения по времени. Для текущего набора треков это изменение не повлияло на ваш счет (и, вероятно, никогда не будет).
Мартин Эндер