Vectory에게! – 벡터 레이싱 그랑프리


39

사용자 CarpetPython은 검색 공간의 증가로 인해 휴리스틱 솔루션에 훨씬 더 초점을 두는 이 문제에 대해 새로운 견해를 발표했습니다 . 나는 개인적으로 도전이 나의 것보다 훨씬 좋다고 생각하므로 그 시도를 해보십시오!

벡터 레이싱은 중독성있는 게임으로 펜과 사각형 종이를 사용하여 재생할 수 있습니다. 종이에 임의의 경마장을 그리고 시작과 끝을 정의한 다음 턴 기반 방식으로 포인트 크기의 자동차를 조종합니다. 당신이 할 수있는 한 빨리 끝까지 도착하지만 벽에 끝나지 않도록 조심하십시오!

트랙

  • 지도는 2 차원 격자이며 각 셀에는 정수 좌표가 있습니다.
  • 그리드 셀에서 이동합니다.
  • 각 격자 셀은 트랙의 일부이거나 벽입니다.
  • 정확히 하나의 트랙 셀이 시작 좌표입니다.
  • 하나 이상의 트랙 셀이 목표로 지정됩니다. 이 중 하나에 착륙 하면 레이스가 완료됩니다. 여러 목표 셀이 반드시 연결되어 있지는 않습니다.

자동차 조타

자동차는 주어진 좌표와 속도 벡터로 시작 (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 에 자체 연결되지 않으므로 봇을 개발하는 동안 필요할 수있는 모든 종류의 디버그 출력에 자유롭게 사용할 수 있습니다. 그러나 제출 된 코드에서 이러한 출력을 제거하거나 주석 처리하십시오.

봇이 한 턴마다 응답하는 데 0.5 초가 걸릴 수 있습니다. 더 오래 걸리는 턴의 경우, 시간 예산 (트랙 당)이 target/2초입니다. 턴이 0.5 초 이상 걸릴 때마다 시간 예산에서 추가 시간이 차감됩니다. 시간 예산이 0에 도달하면 현재 레이스가 중단됩니다.

새로운 : 실용적인 이유로, 나는 (메모리가 더 합리적 트랙 크기 시간보다 제한 될 것으로 보인다 때문에) 메모리 제한을 설정해야합니다. 따라서 프로세스 탐색기 에서 Private Bytes 로 측정 한 레이서가 1GB 이상의 메모리를 사용하는 테스트 실행을 중단해야합니다 .

채점

20 개 트랙 의 벤치 마크 가 있습니다. 각 트랙마다 :

  • 트랙을 완료하면 점수는 목표 셀에 도달하는 데 필요한 이동 횟수를로 나눕니다target .
  • 당신은 시간 / 메모리가 부족하면 또는 목표에 도달하지 전에 target턴이 경과 하거나 언제든지 아웃 오브 바운드 / 벽에 착륙, 당신의 점수입니다 2.
  • 만약 당신의 프로그램이 결정적이지 않다면, 당신의 점수는 그 트랙에서 평균 10 번 이상 뛰는 것입니다 (답에 이것을 적어주십시오).

전체 점수는 개별 트랙 점수의 합계입니다. 최저 점수가 이깁니다!

또한 모든 참가자는 추가 벤치 마크 트랙 을 제공 할 수 있으며 공식 목록에 추가 될 것입니다. 이 새로운 트랙을 포함하여 이전 답변이 재평가됩니다. 이는 솔루션이 기존 테스트 사례에 너무 가깝게 조정되지 않도록하고 내가 놓쳤을 수있는 흥미로운 사례를 설명하기위한 것입니다.

타이 브레이킹

이미 최적의 솔루션이 있으므로 참가자 점수의 주요 요소가 될 것입니다.

동점이있는 경우 (모든 트랙을 최적으로 또는 다르게 해결하는 여러 답변으로 인해) 동점을 깨기 위해 추가 (더 큰) 테스트 사례를 추가합니다. 타이 브레이커를 만들 때 사람의 편견을 피하기 위해 고정 된 방식으로 생성 됩니다.

  • 이 방법으로 생성 된 마지막 트랙과 비교 하여 측면 길이 n를 늘 10립니다. (넥타이를 깨지 않으면 크기를 건너 뛸 수 있습니다.)
  • 기초는 이 벡터 그래픽입니다
  • Mathematica 스 니펫을 사용하여 원하는 해상도로 래스터 화됩니다 .
  • 시작은 왼쪽 상단에 있습니다. 특히 트랙 끝의 맨 위 행의 가장 왼쪽 셀이됩니다.
  • 목표는 오른쪽 하단에 있습니다. 특히 트랙 끝에서 맨 아래 행의 가장 오른쪽 셀이됩니다.
  • target의지 4*n.

초기 벤치 마크의 최종 트랙은 이미 이와 같이 생성되었습니다 n = 50.

컨트롤러

제출물을 테스트하는 프로그램은 Ruby로 작성되었으며 GitHub 에서 사용할 벤치 마크 파일과 함께 찾을 수 있습니다 . 거기에 호출 randomracer.rb된 봇 예제도 있습니다.이 로봇은 단순히 임의의 움직임을 선택합니다. 기본 구조를 봇의 시작 지점으로 사용하여 통신이 어떻게 작동하는지 확인할 수 있습니다.

다음과 같이 선택한 트랙 파일에 대해 자신의 봇을 실행할 수 있습니다.

ruby controller.rb track_file_name command to run your racer

예 :

ruby controller.rb benchmark.txt ruby randomracer.rb

저장소는 또한 두 개의 클래스가 들어 Point2DTrack. 제출물이 Ruby로 작성된 경우 편의를 위해 자유롭게 재사용하십시오.

명령 줄 스위치

당신은 명령 줄 스위치를 추가 할 수 있습니다 -v, -s, -t벤치 마크의 파일 이름 앞에. 여러 스위치를 사용하려는 경우 예를 들어을 수행 할 수도 있습니다 -vs. 이것이 그들이하는 일입니다.

-v (verbose) : 컨트롤러에서 더 많은 디버그 출력을 생성하려면이 옵션을 사용하십시오.

-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 VM에서도 시도해 볼 수 있습니다. 그러나 속도가 상당히 느려질 수 있으므로 시간 제한을 초과하려면 프로그램이 Windows에서 실행되는지 확인하십시오.

최고의 드라이버가 벡터 적으로 등장하기를 바랍니다!

그러나 나는 놀고 싶다!

더 나은 느낌을 얻기 위해 게임을 직접 시도하고 싶다면 이 구현이 있습니다 . 거기에 사용 된 규칙은 약간 더 정교하지만 유용 할 정도로 비슷합니다.

리더 보드

최종 업데이트 : 2014 년 1 월 9 일 , 21:29 UTC
벤치 마크 트랙 : 25
타이 브레이커 크기 : 290, 440

  1. 6.86688 – 쿠 로이 네코
  2. 8.73108 – 사용자 2357112-2 차 제출
  3. 9.86627 – 테 네노
  4. 10.66109 – user2357112-첫 번째 제출
  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 ++ 언어는 성냥개비를 움직이기 위해 농구대를 뛰어 넘을 수있는 요령을 가지고 있습니다. 그러나 비교적 빠르고 메모리 효율적인 코드를 생성 할 수 있습니다.

해싱

여기서 핵심은 노드에 좋은 해시 테이블을 제공하는 것입니다. 이것이 실행 속도의 주요 요소입니다. (GNU와 Microsoft)
두 가지 구현으로 unordered_set30 %의 실행 속도 차이가 나왔습니다 (GNU, yay!).

그 차이는 실제로 놀랍지 않습니다 unordered_set. 코드의 트럭로드가 숨겨져있는 것 입니다.

호기심으로 해시 테이블의 최종 상태에 대한 통계를 작성했습니다.
두 알고리즘 모두 거의 동일한 버킷 / 요소 비율로
끝나지만 재 파티셔닝은 다양합니다. 290x290 타이 브레이커의 경우 GNU는 비어 있지 않은 버킷 당 평균 1.5 개의 요소를 가져오고 Microsoft는 5.8 (!)입니다.

내 해싱 함수가 Microsoft에서 잘 무작위 화하지 않은 것 같습니다 ... 레드몬드의 사람들이 실제로 STL을 벤치마킹했는지 또는 내 유스 케이스가 GNU 구현을 선호하는지 궁금합니다 ...

물론, 내 해싱 함수는 최적의 위치에 없습니다. 다중 시프트 / mul을 기반으로 일반적인 정수 믹싱을 사용할 수 있었지만 해시 효율적인 함수를 계산하는 데 시간이 걸립니다.

해시 테이블 쿼리 수가 삽입 수에 비해 매우 높은 것으로 보입니다. 예를 들어 290x290 타이 브레이커에는 2270 만 건의 쿼리에 약 360 만 건의 삽입이 있습니다.
이와 관련하여 차선책이지만 빠른 해싱은 더 나은 성능을 제공합니다.

메모리 할당

효율적인 메모리 할당자를 제공하는 것이 두 번째입니다. 성능이 약 30 % 향상되었습니다. 추가 된 쓰레기 코드의 가치가 있는지 여부는 논쟁의 여지가 있습니다 :).

현재 버전은 노드 당 40-55 바이트를 사용합니다.
기능 데이터에는 노드에 24 바이트가 필요합니다 (4 개의 좌표 및 2 개의 포인터).
미친 100.000 줄 테스트 사례로 인해 좌표는 4 바이트 단어로 저장해야합니다. 그렇지 않으면 short (최대 좌표 값 32767)를 사용하여 8 바이트를 얻을 수 있습니다. 나머지 바이트는 대부분 순서가없는 세트의 해시 테이블에서 사용됩니다. 이는 데이터 처리가 실제로 "유용한"페이로드보다 약간 더 많이 소비 함을 의미합니다.

그리고 승자는...

내 PC에서 Win7에서는 타이 브레이커 (케이스 23, 290x290)가 약 2.2 초 만에 최악의 버전 (예 : Microsoft 컴파일)으로 약 185Mb의 메모리 소비로 해결됩니다.
비교를 위해 현재 리더 (user2357112의 Python 코드)는 30 초 이상 걸리며 약 780Mb를 소비합니다.

컨트롤러 문제

생명을 구하기 위해 루비로 코드를 작성할 수 있을지 잘 모르겠습니다.
그러나 컨트롤러 코드에서 두 가지 문제를 발견하고 해킹했습니다.

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 ​​초 소요, 600Mb 피크)!

290x290 타이 브레이커 표면의 절반 미만으로 노드 방문이 약 3 배 더 필요합니다. 반면에 휴리스틱 / A * 기반 솔버가 쉽게 이길 수 있습니다.

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

보너스 : 동등한 (그러나 다소 덜 효율적인) PHP 버전

고유 언어의 속도 저하로 인해 C ++ 사용을 확신하기 전에이 작업을 시작했습니다.
PHP 내부 해시 테이블은 적어도이 특별한 경우에는 Python만큼 효율적으로 보이지 않습니다. :).

<?php

class Trace {
    static $file;
    public static $state_member;
    public static $state_target;
    static function msg ($msg)
    {
        fputs (self::$file, "$msg\n");
    }

    static function dump ($var, $msg=null)
    {
        ob_start();
        if ($msg) echo "$msg ";
        var_dump($var);
        $dump=ob_get_contents();
        ob_end_clean();
        fputs (self::$file, "$dump\n");
    }

    function init ($fname)
    {
        self::$file = fopen ($fname, "w");
    }
}
Trace::init ("racer.txt");

class Point {
    public $x;
    public $y;

    function __construct ($x=0, $y=0)
    {
        $this->x = (float)$x;
        $this->y = (float)$y;
    }

    function __toString ()
    {
        return "[$this->x $this->y]";
    }

    function add ($v)
    {
        return new Point ($this->x + $v->x, $this->y + $v->y);
    }

    function vector_to ($goal)
    {
        return new Point ($goal->x - $this->x, $goal->y - $this->y);
    }
}

class Node {
    public $posx  , $posy  ;
    public $speedx, $speedy;
    private $parent;

    public function __construct ($posx, $posy, $speedx, $speedy, $parent)
    {
        $this->posx = $posx;
        $this->posy = $posy;
        $this->speedx = $speedx;
        $this->speedy = $speedy;
        $this->parent = $parent;
    }

    public function path ()
    {
        $res = array();
        $v = new Point ($this->speedx, $this->speedy);
        for ($node = $this->parent ; $node != null ; $node = $node->parent)
        {
            $nv = new Point ($node->speedx, $node->speedy);
            $a = $nv->vector_to ($v);
            $v = new Point ($node->speedx, $node->speedy);
            array_unshift ($res, $a);
        }
        return $res;
    }
}

class Map {

    private static $target;       // maximal number of turns
    private static $time;         // time available to solve
    private static $sx, $sy;      // map dimensions
    private static $cell;         // cells of the map
    private static $start;        // starting point
    private static $acceleration; // possible acceleration values

    public static function init ()
    {
        // read map definition
        self::$target = trim(fgets(STDIN));
        list (self::$sx, self::$sy) = explode (" ", trim(fgets(STDIN)));
        self::$cell = array();
        for ($y = 0 ; $y != self::$sy ; $y++) self::$cell[] = str_split (trim(fgets(STDIN)));
        self::$time = trim(fgets(STDIN));

        // get starting point
        foreach (self::$cell as $y=>$row)
        {
            $x = array_search ("S", $row);
            if ($x !== false)
            {
                self::$start = new Point ($x, $y);
Trace::msg ("start ".self::$start);
                break;
            }
        }

        // compute possible acceleration values
        self::$acceleration = array();
        for ($x = -1 ; $x <= 1 ; $x++)
        for ($y = -1 ; $y <= 1 ; $y++)
        {
            self::$acceleration[] = new Point ($x, $y);
        }
    }

    public static function solve ()
    {
        $now = microtime(true);
        $res = array();
        $border = array (new Node (self::$start->x, self::$start->y, 0, 0, null));
        $present = array (self::$start->x." ".self::$start->y." 0 0" => 1);
        while (count ($border))
        {
if ((microtime(true) - $now) > 1)
{
Trace::msg (count($present)." nodes, ".round(memory_get_usage(true)/1024)."K");
$now = microtime(true);
}
            $node = array_shift ($border);
//Trace::msg ("node $node->pos $node->speed");
            $px = $node->posx;
            $py = $node->posy;
            $vx = $node->speedx;
            $vy = $node->speedy;
            foreach (self::$acceleration as $a)
            {
                $nvx = $vx + $a->x;
                $nvy = $vy + $a->y;
                $npx = $px + $nvx;
                $npy = $py + $nvy;
                if ($npx < 0 || $npx >= self::$sx || $npy < 0 || $npy >= self::$sy || self::$cell[$npy][$npx] == "#")
                {
//Trace::msg ("invalid position $px,$py $vx,$vy -> $npx,$npy");
                    continue;
                }
                if (self::$cell[$npy][$npx] == "*")
                {
Trace::msg ("winning position $px,$py $vx,$vy -> $npx,$npy");
                    $end = new Node ($npx, $npy, $nvx, $nvy, $node);
                    $res = $end->path ();
                    break 2;
                }
//Trace::msg ("checking $np $nv");
                $signature = "$npx $npy $nvx $nvy";
                if (isset ($present[$signature])) continue;
//Trace::msg ("*** adding $np $nv");
                $border[] = new Node ($npx, $npy, $nvx, $nvy, $node);
                $present[$signature] = 1;
            }
        }
        return $res;
    }
}

ini_set("memory_limit","1000M");
Map::init ();
$res = Map::solve();
//Trace::dump ($res);
foreach ($res as $a) echo "$a->x $a->y\n";
?>

erf ... 내 베어 본 할당자는 약간 너무 베어 본입니다. 그런 다음 g ++에서 작동하도록 필요한 쓰레기를 추가합니다. 미안합니다.

알았어, 고쳤다. g ++ 버전은 실제로 약 30 % 더 빠르게 작동합니다. 이제 stderr에 몇 가지 통계를 출력합니다. (소스의 마지막 줄에서) 자유롭게 주석을 달아주십시오. 실수로 다시 죄송합니다.

좋아, 그것은 지금 작동하고 나는 당신의 점수를 재현했습니다. 정말 빠릅니다! :) 테스트 케이스를 벤치 마크에 추가하지만 목표를로 변경합니다 400. 타이 브레이커를 제외한 다른 모든 목표를 결정한 방식과 일치합니다. 다른 모든 제출물을 다시 가져 오면 기본 게시물을 업데이트하겠습니다.
Martin Ender

결과를 업데이트했습니다. 다른 모든 제출물은 테스트 트랙의 메모리 제한을 초과하므로 타이 브레이커가 필요하지 않았습니다. 축하합니다! :)
Martin Ender

감사. 실제로이 과제는 이러한 STL 해시 테이블을 파헤칠 기회를 주었다. C ++ 내장을 싫어하지만 호기심에 사로 잡히지 않고 도움을 줄 수는 없습니다. 야옹! :).

10

C ++, 5.4 (결정적, 최적)

동적 프로그래밍 솔루션. 아마도 최적입니다. 매우 빠름 : 0.2에서 20 개의 테스트 케이스를 모두 해결합니다. 64 비트 시스템에서 특히 빠릅니다. 보드가 각 방향으로 32,000 개 미만의 장소라고 가정합니다.

이 레이서는 조금 특이합니다. 시작 라인에서 최적의 경로를 계산 한 후 계산 된 경로를 즉시 실행합니다. 시간 제어를 무시하고 적시에 최적화 단계를 완료 할 수 있다고 가정합니다 (합리적으로 최신 하드웨어에 해당됨). 지나치게 큰지도에서는 ​​레이서가 segfault 할 가능성이 적습니다. 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 2018 년

레이서가 테스트 케이스에서 어떻게 수행합니까?
user2357112 2016 년

0.14 (14 이동)
nneonneo 2016 년

그 시간이 걸리거나 이동 / 목표입니까? 이동 / 목표 인 경우 시간 측면에서 어떻게 수행됩니까?
user2357112 2016 년

1
사이클 예방 코드에서 버그를 발견 한 것 같습니다. 각 상태에 대해 검색이 상태 S에서 도달한다고 가정하면 최적 경로는 S로 돌아갈 수 없습니다. 최적 경로 S로 돌아 오면 상태가 최적 경로에 있지 않을 수 있습니다 (우리가 할 수 있기 때문에) 루프를 제거하고 더 짧은 경로를 얻으십시오), 그래서 우리는 그 상태에 대해 너무 높은 결과를 얻는 지 신경 쓰지 않습니다. 그러나 최적의 경로가 A와 B의 순서대로 통과하지만 B가 스택에있는 동안 검색에서 먼저 A를 찾은 경우 루프 방지 기능에 의해 A의 결과가 손상됩니다.
user2357112

6

파이썬 2 , 결정 론적, 최적

여기 내 레이서가 있습니다. 벤치 마크에서 테스트하지는 않았지만 (루비의 버전과 설치 프로그램을 설치하는 데 여전히 어려움을 겪고 있음) 모든 것을 최적의 시간 제한으로 해결해야합니다. 실행 명령은 python whateveryoucallthefile.py입니다. -s컨트롤러 플래그가 필요합니다 .

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

import operator
import time

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

class ImpossibleRaceError(Exception): pass

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

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

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

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

    return start, walls, goals

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

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

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

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

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

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

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

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

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

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

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

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

if __name__ == '__main__':
    main()

nneonneo의 경주자를 검사 한 후 (실제로는 C ++ 컴파일러가 없기 때문에 실제로 테스트하지는 않음) 목표가 얼마나 근접했는지 또는 얼마나 짧은 지에 관계없이 상태 공간을 거의 철저하게 검색하는 것으로 나타났습니다 경로가 이미 발견되었습니다. 또한 시간 규칙은 길고 복잡한 솔루션으로 맵을 작성하는 데 길고 지루한 시간 제한이 필요하다는 것을 알았습니다. 따라서 내지도 제출은 매우 간단합니다.

새로운 테스트 케이스

(GitHub는 긴 줄을 표시 할 수 없습니다. 트랙은입니다 *S.......[and so on].....)


추가 제출 : Python 2, 양방향 검색

이것은 너비 우선 제출을 최적화하려고 할 때 약 2 개월 전에 작성한 접근법입니다. 당시 존재했던 테스트 케이스의 경우 개선이 없었으므로 제출하지 않았지만 쿠 로이의 새로운 맵의 경우 메모리 캡 아래에서 간신히 쥐어 짜는 것 같습니다. 나는 여전히 kuroi의 솔버가 이것을 이길 것으로 기대하지만, 그것이 어떻게 유지되는지에 관심이 있습니다.

# 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

@ Ray 나는 오류 메시지도 얻지 만 항상 그 결과를 얻습니다. 컨트롤러가 이미 완료되었지만 레이서 프로세스를 종료하려고하는 것처럼 보이기 때문에 컨트롤러에있는 것일 수 있습니다.
Martin Ender

@ m.buettner 이유를 찾았습니다 .-s를 추가하면 괜찮습니다.
Ray

@ 레이 오 그래, 내가하고있어. 결과가 이미 있지만 컨트롤러가 프로세스를 종료하려고 할 때 트랙 13 및 14에 여전히 오류가 발생합니다. 나는 그것을 조사해야한다고 생각하지만, 점수에 영향을 미치지 않으므로 아직 귀찮게하지 않았습니다.
마틴 엔더

불행히도 다른 규칙을 추가해야했습니다. 이 도전에서 메모리는 시간보다 더 제한적인 것으로 보이므로 메모리 소비를 제한하기 위해 열심히 설정해야했습니다. 레이서가 1GB 이상의 메모리를 사용하는 실행은 시간 제한을 초과하는 것과 동일한 효과로 중단됩니다. 현재 트랙 세트의 경우이 변경으로 인해 악보가 영향을받지 않았습니다. (타이 브레이커에 대한 한계에 도달 할 것으로 생각합니다 n = 400.) 최적화를 적용하면 알려 주시면 테스트를 다시 실행할 수 있습니다.
Martin Ender

3

파이썬 3 : 6.49643 (최적, BFS)

이전 20 건의 벤치 마크 파일은 5.35643 점을 받았습니다. @nneonneo의 솔루션은 5.4이기 때문에 최적이 아닙니다. 아마도 몇 가지 버그가 있습니다.

이 솔루션은 BFS를 사용하여 그래프를 검색하며 각 검색 상태는 (x, y, dx, dy) 형식입니다. 그런 다음지도를 사용하여 주에서 거리로 매핑합니다. 최악의 경우 시간과 공간의 복잡성은 O (n ^ 2 m ^ 2)입니다. 속도가 너무 높지 않거나 레이서가 충돌하기 때문에 거의 발생하지 않습니다. 실제로 22 개의 테스트 케이스를 모두 완료하는 데 3 초가 걸렸습니다.

from collections import namedtuple, deque
import itertools

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

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

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

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

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

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

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

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

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

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

main()

# 결과

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

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

 No.       Size     Target   Score     Details
-------------------------------------------------------------------------------------
  1       37 x 1        36   0.22222   Racer reached goal at ( 36, 0) in 8 turns.
  2       38 x 1        37   0.24324   Racer reached goal at ( 37, 0) in 9 turns.
  3       33 x 1        32   0.25000   Racer reached goal at ( 32, 0) in 8 turns.
  4       10 x 10       10   0.40000   Racer reached goal at ( 7, 7) in 4 turns.
  5        9 x 6         8   0.37500   Racer reached goal at ( 6, 0) in 3 turns.
  6       15 x 7        16   0.37500   Racer reached goal at ( 12, 4) in 6 turns.
  7       17 x 8        16   0.31250   Racer reached goal at ( 14, 0) in 5 turns.
  8       19 x 13       18   0.27778   Racer reached goal at ( 0, 11) in 5 turns.
  9       60 x 10      107   0.14953   Racer reached goal at ( 0, 6) in 16 turns.
 10       31 x 31      106   0.23585   Racer reached goal at ( 27, 0) in 25 turns.
 11       31 x 31      106   0.24528   Racer reached goal at ( 15, 15) in 26 turns.
 12       50 x 20       50   0.24000   Racer reached goal at ( 49, 10) in 12 turns.
 13      100 x 100    2600   0.01385   Racer reached goal at ( 50, 0) in 36 turns.
 14       79 x 63      242   0.24380   Racer reached goal at ( 3, 42) in 59 turns.
 15       26 x 1        25   0.32000   Racer reached goal at ( 25, 0) in 8 turns.
 16       17 x 1        19   0.52632   Racer reached goal at ( 16, 0) in 10 turns.
 17       50 x 1        55   0.34545   Racer reached goal at ( 23, 0) in 19 turns.
 18       10 x 7        23   0.34783   Racer reached goal at ( 1, 3) in 8 turns.
 19       55 x 55       45   0.17778   Racer reached goal at ( 50, 26) in 8 turns.
 20      101 x 100     100   0.14000   Racer reached goal at ( 99, 99) in 14 turns.
 21   100000 x 1         1   1.00000   Racer reached goal at ( 0, 0) in 1 turns.
 22       50 x 50      200   0.05500   Racer reached goal at ( 47, 46) in 11 turns.
-------------------------------------------------------------------------------------
TOTAL SCORE:                 6.49643

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

예, user2357112의 의견에 따르면 nneonneo의주기 예방에 버그가 있습니다. 내가 아는 한 속도는 사각형 그리드에서 O(√n)구현하도록 제한됩니다 O(n³)(다른 그리드와 동일). 오늘 귀하의 제출 대 사용자 2357112의 점수를 매기기 위해 타이 브레이커를 추가하겠습니다.
Martin Ender

Btw, 다른 테스트 사례를 추가 할 계획입니까?
Martin Ender

@ m.buettner 아니요,이 게임에 대한 이해력이 충분하지 않습니다. 그래서 내 테스트 케이스는 흥미로운 것이 아닙니다.
Ray

불행히도 다른 규칙을 추가해야했습니다. 이 도전에서 메모리는 시간보다 더 제한적 인 것처럼 보이므로 메모리 소비를 제한하기 위해 열심히 설정해야했습니다. 레이서가 1GB 이상의 메모리를 사용하는 실행은 시간 제한을 초과하는 것과 동일한 효과로 중단됩니다. 이 규칙을 사용하면 제출이 타이 브레이커 크기의 제한을 가장 먼저 초과 n=270하므로 다른 두 개의 "최적"제출에 뒤지지 않습니다. 즉, 귀하의 제출물도 세 가지 중 가장 느리므로 어쨌든 큰 타이 브레이커로 세 번째 였을 것입니다.
Martin Ender

최적화를 적용하면 알려 주시면 테스트를 다시 실행할 수 있습니다.
마틴 엔더

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컨트롤러가있는 디렉토리 대신 파일이 있는 디렉토리에서 파일 을 실행해야했지만 실행 중입니다. 테스트 케이스를 추가하기로 결정한 경우 (댓글 포함) 핑 (Ping me)하여 벤치 마크에 추가 할 수 있습니다. 점수는 10 회 이상 약 33 회 였지만 (리더 보드 참조) 벤치 마크에 추가되는 모든 새로운 테스트 트랙에 따라 변경 될 수 있습니다.
Martin Ender 2016 년

아 다른 디렉토리에서도 실행할 수 있습니다. 커맨드 라인에서 Java에 익숙하지 않은 분들을 위해 :java -cp path/to/class/file VectorRacing
Martin Ender

아, 그래 나는 엄청나게 많은 수업을 만들었습니다 (13, 정확히 말하면). 나는 항상 클래스 디렉토리에서 스크립트를 실행하고 있었으므로 실제로 테스트하지는 않았습니다. 테스트 케이스를 만들 수도 있지만, 무작위가 아닌 레이서를 만들려고합니다.
pseudonym117

확실한. 그럴 경우 별도의 답변으로 추가하십시오. (각각 하나의 테스트 케이스를 자유롭게 추가 할 수 있습니다.)
Martin Ender

불행히도 다른 규칙을 추가해야했습니다. 이 도전에서 메모리는 시간보다 더 제한적 인 것처럼 보이므로 메모리 소비를 제한하기 위해 열심히 설정해야했습니다. 레이서가 1GB 이상의 메모리를 사용하는 실행은 시간 제한을 초과하는 것과 동일한 효과로 중단됩니다. 현재 트랙 세트의 경우이 변경으로 인해 악보가 영향을받지 않았습니다 (아마도).
Martin Ender
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.