Формування Поліміно з ланцюжком стрижнів


20

Фон

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

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

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

Цей перший стержень темно-синього кольору, а потім ми проходимо багатокутник у напрямку проти годинникової стрілки .

Почуття обертання не впливає на результат у наведеному вище прикладі. Але давайте розглянемо інший ланцюг [3, 1, 1, 1, 2, 1, 1], який дає наступні 3 поліоміно:

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

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

Якби у нас був більш гнучкий ланцюжок однакової довжини, [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

Ви знайдете вхідний файл з цим тут .

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

   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

doze #defines tho
Soham Chowdhury

За відсутності будь-яких інших відповідей ... ось, майте щедрість!
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 циклічних перестановок iff A підрядком методу B + B (я не маю поняття, чому я цього не робив раніше).
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.