Формирование Полемино с Цепочкой Прутков

20

Фон

Рассмотрим (замкнутую) цепочку стержней, каждый из которых имеет целочисленную длину. Сколько разных полимино без дырок вы можете сформировать с данной цепью? Или, другими словами, сколько разных несамопересекающихся многоугольников с выровненными осями сторонами вы можете сформировать с данной цепочкой?

Давайте посмотрим на пример. Рассмотрим конкретную цепочку, состоящую из 8 стержней длиной 1 и 2, которые мы можем представить как [1, 1, 2, 2, 1, 1, 2, 2]. До поворотов и трансляций есть только 8 возможных полиомино (мы учитываем разные отражения):

введите описание изображения здесь

Этот первый стержень темно-синего цвета, а затем мы пересекаем многоугольник против часовой стрелки .

Чувство вращения не влияет на результат в приведенном выше примере. Но давайте рассмотрим еще одну цепочку [3, 1, 1, 1, 2, 1, 1], которая дает следующие 3 полиомино:

введите описание изображения здесь

Обратите внимание, что мы не включаем отражение последнего polyomino, потому что это потребует обхода по часовой стрелке.

Если бы у нас была более гибкая цепочка такой же длины, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]мы могли бы фактически сформировать оба отражения среди некоторых других полионино, всего 9:

введите описание изображения здесь

Соревнование

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

Пожалуйста, напишите полную программу и включите команды для компиляции (если применимо) и запуска вашего кода из командной строки. Пожалуйста, включите ссылку на бесплатный компилятор / переводчик для вашего языка.

Ваша программа должна читать входные данные из STDIN. Первая строка будет содержать целое число M . Следующие M строк будут тестовыми примерами, каждый из которых будет разделенным пробелами списком длин стержней. Ваша программа должна напечатать M строк в STDOUT, каждая из которых состоит из единственного целого числа - количества различных полиомино, которые могут быть сформированы.

Вы должны использовать только один поток.

Ваша программа не должна использовать более 1 ГБ памяти в любое время. (Это не совсем строгий предел, но я буду следить за использованием памяти вашего исполняемого файла и уничтожать любой процесс, который последовательно использует более 1 ГБ, или значительно превышает его.)

Чтобы предотвратить чрезмерное предварительное вычисление, ваш код не должен быть длиннее 20 000 байтов, и вы не должны читать никакие файлы.

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

счет

Я предоставил наборы тестов для цепей из N = 10, 11, ..., 20 стержней. Каждый набор тестов содержит 50 случайных цепочек длиной от 1 до 4 включительно.

Ваш основной балл самый большой N для которого ваша программа завершает весь набор тестов в течение 5 минут (на моей машине под Windows 8). Временной прерыватель будет фактическим временем, затраченным вашей программой на этот тестовый набор.

Если кто-то побеждает самый большой набор тестов, я буду продолжать добавлять более крупные.

Тестовые случаи

Вы можете использовать следующие контрольные примеры, чтобы проверить правильность своей реализации.

Input                            Output

1 1                              0
1 1 1 1                          1
1 1 1 1 1 1                      1
1 1 1 1 1 1 1 1                  3
1 1 1 1 1 1 1 1 1 1              9
1 1 1 1 1 1 1 1 1 1 1 1          36
1 1 1 1 1 1 1 1 1 1 1 1 1 1      157
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1  758
1 1 2 2 1 1 2 2                  8
1 1 2 2 1 1 2 2 1 1              23
1 1 2 2 1 1 2 2 1 1 2 2          69
1 2 1 2 1 2 1 2                  3
1 2 1 2 1 2 1 2 1 2 1 2          37
1 2 3 2 1 2 3 2                  5
1 2 3 2 1 2 3 2 1 2 3 2          23
3 1 1 1 2 1 1                    3
1 2 3 4 5 6 7                    1
1 2 3 4 5 6 7 8                  3
1 2 3 4 5 6 7 8 9 10 11          5
2 1 5 3 3 2 3 3                  4
4 1 6 5 6 3 1 4                  2
3 5 3 5 1 4 1 1 3                5
1 4 3 2 2 5 5 4 6                4
4 1 3 2 1 2 3 3 1 4              18
1 1 1 1 1 2 3 3 2 1              24
3 1 4 1 2 2 1 1 2 4 1 2          107
2 4 2 4 2 2 3 4 2 4 2 3          114

Вы найдете входной файл с этим здесь .

Leaderboard

   User          Language       Max N      Time taken (MM:SS:mmm)

1. feersum       C++ 11         19         3:07:430

2. Sp3000        Python 3       18         2:30:181
Мартин Эндер
источник
«без дыр» кажется излишним. Во-первых, одна непрерывная цепочка не может производить дыроколотые полиомино.
Спарр
Допускается ли многопоточность? И если потоки находятся в разных процессах, каждый ли использует 1 ГБ? : P
feersum
@Sparr Может, когда периметр касается самого себя в углу. Например, см. № 81 здесь. Это не следует считать.
Мартин Эндер
@feersum Для простоты я собираюсь сказать нет многопоточности (и отредактирую задачу).
Мартин Эндер
1
@PeterKagey Вы разместили этот комментарий на неправильный вызов? Похоже, это должно было быть на этом .
Мартин Эндер

Ответы:

5

C ++ 11

Обновления: добавлена ​​первая строка, cкоторая начинается раньше, если расстояние слишком далеко от начала координат (что и было целью переменной rlen, но я забыл написать ее в первой версии). Я изменил его, чтобы использовать гораздо меньше памяти, но ценой времени. Теперь он решает N = 20 всего за 5 минут для меня.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <ctime>

#define M std::map
#define MS 999
#define l (xM*2+1)

#define KITTENS(A,B)A##B
#define CATS(A,B)KITTENS(A,B)
#define LBL CATS(LBL,__LINE__)
#define unt unsigned
#define SU sizeof(unt)
#define SUB (SU*8)
#define newa (nb^SZ||fail("blob"),nb+++blob)

#define D

struct vec {int x, y;};


unt s[MS*2];
int xM, sl[MS];
vec X[MS];

struct a;
struct a  { M<unt,unt>v;};

#define SZ ((1<<29)/sizeof(a))
a*blob;
unt nb;


int fail(const char*msg)
{
    printf("failed:%s", msg);
    exit(1);
    return 1;
}

struct
{
    unt*m;
    bool operator()(int x, int y) { return m[(x+l*y)/SUB] >> (x+l*y)%SUB & 1; }
    void one(int x, int y) { m[(x+l*y)/SUB] |= 1U << (x+l*y)%SUB; }
    void zero(int x, int y) { m[(x+l*y)/SUB] &= ~(1U << (x+l*y)%SUB); }
} g;

unt c(a*A, vec x, unt rlen, unt sn) {
    if((unt)x.y+abs(x.x) > rlen) return 0;
    if(!rlen) {
        vec *cl=X, *cr=X, *ct=X;
        for(unt i=1; i<sn; i++) {
            #define BLAH(Z,A,B,o,O) \
                if(X[i].A o Z->A || (X[i].A == Z->A && X[i].B O Z->B)) \
                   Z = X+i

            BLAH(cl,x,y,<,>);
            BLAH(cr,x,y,>,<);
            BLAH(ct,y,x,>,>);
        }
        unt syms = 1;
        #define BLA(H,Z) {bool sy=1;for(unt o=0; o<sn; o++) sy &= (int)(1|-(H))*sl[o] == sl[(Z-X+o)%sn]; syms += sy;}
        BLA(~o&1,cl)
        BLA(1,ct)
        BLA(o&1,cr)

        #ifdef D
            //printf("D");for(int i=0;i<sn;i++)printf(" %u",sl[i]);printf("\n");
            if(syms==3) fail("symm");
        #endif

        return syms;
    }
    if(!(x.x|x.y|!sn)) return 0;
    X[sn] = x;

    unt k = 0;
    for(auto it: A->v) {
        int len = it.first;
        bool ve = sn&1;
        int dx = ve?0:len, dy = ve?len:0;

        #define PPCG(O)(x.x O (ve?0:z), x.y O (ve?z:0))
        #define MACR(O) { \
            vec v2 = {x.x O dx, x.y O dy}; \
            if(v2.y<0||(!v2.y&&v2.x<0)||abs(v2.x)>xM||v2.y>xM) \
                goto LBL; \
            for(int z=1; z<=len; z++) \
                if(g PPCG(O)) \
                    goto LBL; \
            for(int z=1; z<=len; z++) \
                g.one PPCG(O); \
            sl[sn] = O len; \
            k += c(blob+it.second, v2, rlen - len, sn+1); \
            for(int z=1; z<=len; z++) \
                g.zero PPCG(O); \
            } LBL: \

    MACR(+);
    MACR(-);
    }

    return k;
}

void stuff(a *n, unt j, unt r, unt len1)
{
    unt t=0;
    for(unt i=j; i<j+r; i++) {
        t += s[i];
        if((int)t > xM || (len1 && t>len1)) break;
        if(len1 && t < len1) continue;
        int r2 = r-(i-j)-1;
        if(r2) {
            unt x;
            if(n->v.count(t))
                x = n->v[t];
            else
                n->v[t] = x = newa - blob;
            stuff(blob+x, i+1, r2, 0);
        } else n->v[t] = -1;
    }
}

int main()
{
    time_t tim = time(0);
    blob = new a[SZ];
    int n;
    scanf("%u",&n);
    while(n--) {
        nb = 0;
        unt ns=0, tl=0;
        while(scanf("%u",s+ns)) {
            tl += s[ns];
            if(++ns==MS) return 1;
            if(getchar() < 11) break;
        }
        xM = ~-tl/2;
        g.m = (unt*)calloc((xM+1)*l/SU + 1,4);

        memcpy(s+ns, s, ns*SU);

        unt ans = 0;
        for(unt len1 = 1; (int)len1 <= xM; len1++) {
            a* a0 = newa;
            for(unt i=0; i<ns; i++)
                stuff(a0, i, ns, len1);
            ans += c(a0, {}, tl, 0);
            for(unt i=0; i<nb; i++)
                blob[i].v.clear();
        }
        printf("%d\n", ans/4);
        free(g.m);
    }

    tim = time(0) - tim;
    printf("time:%d",(int)tim);
    return 0;
}

Компилировать с

g++ --std=c++11 -O3 feersum.cpp -o feersum.exe
feersum
источник
doze #defines tho
Сохам Чоудхури
В отсутствие каких-либо других ответов ... здесь, щедрость!
Sp3000
3

Python 3 (с PyPy ) - N = 18

ANGLE_COMPLEMENTS = {"A": "C", "F": "F", "C": "A"}
MOVE_ENUMS = {"U": 0, "R": 1, "D": 2, "L": 3}
OPPOSITE_DIR = {"U": "D", "D": "U", "L": "R", "R": "L", "": ""}

def canonical(angle_str):
    return min(angle_str[i:] + angle_str[:i] for i in range(len(angle_str)))

def to_angles(moves):
    """
    Convert a string of UDLR to a string of angles where
      A -> anticlockwise turn
      C -> clockwise turn
      F -> forward
    """

    angles = []

    for i in range(1, len(moves)):
        if moves[i] == moves[i-1]:
            angles.append("F")
        elif (MOVE_ENUMS[moves[i]] - MOVE_ENUMS[moves[i-1]]) % 4 == 1:
            angles.append("C")
        else:
            angles.append("A")

    if moves[0] == moves[len(moves)-1]:
        angles.append("F")
    elif (MOVE_ENUMS[moves[0]] - MOVE_ENUMS[moves[len(moves)-1]]) % 4 == 1:
        angles.append("C")
    else:
        angles.append("A")

    return "".join(angles)

def solve(rods):
    FOUND_ANGLE_STRS = set()

    def _solve(rods, rod_sum, point=(0, 0), moves2=None, visited=None, last_dir=""):
        # Stop when point is too far from origin
        if abs(point[0]) + abs(point[1]) > rod_sum:
            return

        # No more rods, check if we have a valid solution
        if not rods:
            if point == (0, 0):
               angle_str = to_angles("".join(moves2))

               if angle_str.count("A") - angle_str.count("C") == 4:
                   FOUND_ANGLE_STRS.add(canonical(angle_str))

            return

        r = rods.pop(0)

        if not visited:
            visited = set()
            move_dirs = [((r, 0), "R")]
            moves2 = []

        else:
            move_dirs = [((r,0), "R"), ((0,r), "U"), ((-r,0), "L"), ((0,-r), "D")]

        opp_dir = OPPOSITE_DIR[last_dir]

        for move, direction in move_dirs:
            if direction == opp_dir: continue

            new_point = (move[0] + point[0], move[1] + point[1])
            added_visited = set()
            search = True

            for i in range(min(point[0],new_point[0]), max(point[0],new_point[0])+1):
                for j in range(min(point[1],new_point[1]), max(point[1],new_point[1])+1):
                    if (i, j) != point:
                        if (i, j) in visited:
                            search = False

                            for a in added_visited:
                                visited.remove(a)

                            added_visited = set()                            
                            break

                        else:
                            visited.add((i, j))
                            added_visited.add((i, j))

                if not search:
                    break

            if search:
                moves2.append(direction*r)
                _solve(rods, rod_sum-r, new_point, moves2, visited, direction)
                moves2.pop()

            for a in added_visited:
                visited.remove(a)

        rods.insert(0, r)
        return

    _solve(rods, sum(rods))
    return len(FOUND_ANGLE_STRS)

num_rods = int(input())

for i in range(num_rods):
    rods = [int(x) for x in input().split(" ")]
    print(solve(rods))

Беги с ./pypy <filename>.


Это эталонная реализация, которую я написал при обсуждении вопроса с Мартином. Он не был сделан с учетом скорости и довольно хакерский, но он должен обеспечить хорошую основу для начала.

N = 18 занимает около 2,5 минут на моем скромном ноутбуке.

Алгоритм

Вращения проверяются путем преобразования каждой фигуры в серию Fдля прямого, Aдля вращения против часовой стрелки и Cдля вращения по часовой стрелке в каждой точке решетки на границе формы - я называю это угловой строкой . Две формы вращательно идентичны, если их угловые струны являются циклическими перестановками. Вместо того, чтобы всегда проверять это, сравнивая две угловые строки напрямую, когда мы находим новую форму, мы преобразуем ее в каноническую форму перед сохранением. Когда у нас появляется новый кандидат, мы конвертируем в каноническую форму и проверяем, есть ли у нас это уже (таким образом, используя хеширование, а не перебирая весь набор).

Строка угла также используется, чтобы проверить, что форма сформирована против часовой стрелки, убедившись, что число As превышает число Cs на 4.

Самопересечение наивно проверяется путем сохранения каждой точки решетки на границе фигуры и проверки, если точка посещена дважды.

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

Обновления (последние сначала)

  • 6/12: введена каноническая форма, добавлено несколько микрооптимизаций
  • 5/12: исправлена ​​ошибка в объяснении алгоритма. Сделал алгоритм квадратичной проверки циклической перестановки линейным с использованием циклических перестановок A, B, если только подстрока метода B + B (я понятия не имею, почему я не делал этого раньше).
Sp3000
источник