Обчисліть максимальну кількість можливих запусків для якомога більшого рядка


24

[Це питання є подальшим кроком для обчислення прогонів рядка ]

Період pрядка w- це будь-яке додатне ціле число p, яке w[i]=w[i+p] визначається щоразу , коли обидві сторони цього рівняння визначаються. Нехай per(w)позначають розмір найменшого періоду w. Ми говоримо, що рядок wє періодичним iff per(w) <= |w|/2.

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

Для прикладу розглянемо рядок x = abcab. per(abcab) = 3як x[1] = x[1+3] = a, x[2]=x[2+3] = bі менший період не існує. Тому рядок не abcabє періодичним. Однак рядок ababaє періодичним як per(ababa) = 2.

Чим більше прикладів, abcabca, ababababaі abcabcabcтакож є періодичними.

Для тих, хто любить регулярні вирази, цей визначає, чи є рядок періодичним чи ні:

\b(\w*)(\w+\1)\2+\b

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

Підрядка w- це максимальна періодична підрядка (виконання), якщо вона є періодичною і ні, w[i-1] = w[i-1+p]ні w[j+1] = w[j+1-p]. Неофіційно "запуск" не може міститися в більшій кількості "запуску" з тим самим періодом.

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

Виконання (або максимальна періодична підрядка) у рядку T- це інтервал [i...j]з j>=iтаким, що

  • T[i...j] - це періодичне слово з періодом p = per(T[i...j])
  • Це максимально. Формально ні, ні T[i-1] = T[i-1+p]ні T[j+1] = T[j+1-p]. Неофіційно запуск не може міститись у більшій програмі з тим самим періодом.

Позначимо RUNS(T)набором прогонів у рядку T.

Приклади пробіжок

  • Чотири максимальні періодичні підрядка (біжить) в рядку T = atattattє T[4,5] = tt, T[7,8] = tt, T[1,4] = atat, T[2,8] = tattatt.

  • Рядок T = aabaabaaaacaacacмістить наступні 7 максимальних періодичних підрядка (запускається): T[1,2] = aa, T[4,5] = aa, T[7,10] = aaaa, T[12,13] = aa, T[13,16] = acac, T[1,8] = aabaabaa, T[9,15] = aacaaca.

  • Рядок T = atatbatatbмістить наступні три запуски. Вони є T[1, 4] = atat, T[6, 9] = atatі T[1, 10] = atatbatatb.

Тут я використовую 1-індексацію.

Завдання

Напишіть код так, щоб на кожне ціле число n, починаючи з 2, виводили найбільшу кількість запусків, що містяться в будь-якому двійковому рядку довжини n.

Оцінка

Ваш бал - це найвищий результат, який nви досягаєте за 120 секунд, так що для всіх k <= nніхто більше не опублікував правильну відповідь, ніж ви. Зрозуміло, що якщо у вас є всі оптимальні відповіді, ви отримаєте бал за найвищий розмір, який nви опублікували. Однак, навіть якщо ваша відповідь не є оптимальною, ви все одно можете отримати бал, якщо ніхто більше не може його перемогти.

Мови та бібліотеки

Ви можете використовувати будь-яку доступну мову та бібліотеки, які вам подобаються. Там, де це можливо, було б добре запустити свій код, тому, будь-ласка, включіть повне пояснення, як запустити / скомпілювати свій код в Linux, якщо це можливо.

Приклад optima

У наступному: n, optimum number of runs, example string.

2 1 00
3 1 000
4 2 0011
5 2 00011
6 3 001001
7 4 0010011
8 5 00110011
9 5 000110011
10 6 0010011001
11 7 00100110011
12 8 001001100100
13 8 0001001100100
14 10 00100110010011
15 10 000100110010011
16 11 0010011001001100
17 12 00100101101001011
18 13 001001100100110011
19 14 0010011001001100100
20 15 00101001011010010100
21 15 000101001011010010100
22 16 0010010100101101001011

Що саме повинен виводити мій код?

Кожен nваш код повинен виводити один рядок і кількість запусків, які він містить.

My Machine Часи синхронізуються на моїй машині. Це стандартна установка ubuntu на восьмиядерний процесор AMD FX-8350. Це також означає, що мені потрібно мати можливість запускати ваш код.

Провідні відповіді

  • 49 Андерс Kaseorg в C . Одинарна різьба та працює з L = 12 (2 Гб оперативної пам’яті).
  • 27 по cdlane в C .


1
Якщо ви хочете, щоб ми розглядали лише {0,1}рядки, будь ласка, прямо вказати це. Інакше алфавіт може бути нескінченним, і я не бачу, чому ваші тестові вітрини повинні бути оптимальними, бо, здається, ви шукали і лише {0,1}рядки.
недолік

3
@flawr, я шукав рядки по потрійному алфавіту nдо, 12і він ніколи не бив бінарного алфавіту. Евристично я би очікував, що двійковий рядок повинен бути оптимальним, оскільки додавання більшої кількості символів збільшує мінімальну довжину пробігу.
Пітер Тейлор

1
В оптимальних результатах вище "12 7 001001010010", але мій код викачує "12 8 110110011011", де триває період 1 (11, 11, 00, 11, 11), тривалість 3 періоду - (110110, 011011) і є пробіг періоду 4 (01100110) - де я помиляюся під час підрахунку бігу?
cdlane

1
@cdlane 0000 має один запуск. Розглянемо період 000 ... це завжди 1 незалежно від того, скільки нулів.

Відповіді:


9

С

Це робить рекурсивний пошук оптимальних рішень, сильно обрізаних за допомогою верхньої межі кількості прогонів, які можуть бути закінчені невідомою залишком рядка. Для обчислення верхньої межі використовується гігантська таблиця пошуку, розмір якої контролюється постійною L( L=11: 0,5 ГБ,: L=122 ГБ,: L=138 ГБ).

На моєму ноутбуці це відбувається через n = 50 за 100 секунд; наступний рядок приходить за 142 секунди.

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#define N (8*sizeof(unsigned long long))
#define L 13
static bool a[N], best_a[N];
static int start[N/2 + 1], best_runs;
static uint8_t end_runs[2 << 2*L][2], small[N + 1][2 << 2*L];

static inline unsigned next_state(unsigned state, int b)
{
    state *= 2;
    state += b;
    if (state >= 2 << 2*L) {
        state &= ~(2 << 2*L);
        state |= 1 << 2*L;
    }
    return state;
}

static void search(int n, int i, int runs, unsigned state)
{
    if (i == n) {
        int r = runs;
        unsigned long long m = 0;
        for (int p = n / 2; p > 0; p--) {
            if (i - start[p] >= 2*p && !(m & 1ULL << start[p])) {
                m |= 1ULL << start[p];
                r++;
            }
        }
        if (r > best_runs) {
            best_runs = r;
            memcpy(best_a, a, n*sizeof(a[0]));
        }
    } else {
        a[i] = false;
        do {
            int r = runs, bound = 0, saved = 0, save_p[N/2], save_start[N/2], p, s = next_state(state, a[i]);
            unsigned long long m = 0;
            for (p = n/2; p > i; p--)
                if (p > L)
                    bound += (n - p + 1)/(p + 1);
            for (; p > 0; p--) {
                if (a[i] != a[i - p]) {
                    if (i - start[p] >= 2*p && !(m & 1ULL << start[p])) {
                        m |= 1ULL << start[p];
                        r++;
                    }
                    save_p[saved] = p;
                    save_start[saved] = start[p];
                    saved++;
                    start[p] = i + 1 - p;
                    if (p > L)
                        bound += (n - i)/(p + 1);
                } else {
                    if (p > L)
                        bound += (n - (start[p] + p - 1 > i - p ? start[p] + p - 1 : i - p))/(p + 1);
                }
            }
            bound += small[n - i - 1][s];

            if (r + bound > best_runs)
                search(n, i + 1, r, s);
            while (saved--)
                start[save_p[saved]] = save_start[saved];
        } while ((a[i] = !a[i]));
    }
}

int main()
{
    for (int n = 0; n <= N; n++) {
        if (n <= 2*L) {
            for (unsigned state = 1U << n; state < 2U << n; state++) {
                for (int b = 0; b < 2; b++) {
                    int r = 0;
                    unsigned long long m = 0;
                    for (int p = n / 2; p > 0; p--) {
                        if ((b ^ state >> (p - 1)) & 1) {
                            unsigned k = state ^ state >> p;
                            k &= -k;
                            k <<= p;
                            if (!(k & ~(~0U << n)))
                                k = 1U << n;
                            if (!((m | ~(~0U << 2*p)) & k)) {
                                m |= k;
                                r++;
                            }
                        }
                    }
                    end_runs[state][b] = r;
                }
                small[0][state] = end_runs[state][0] + end_runs[state][1];
            }
        }

        for (int l = 2*L < n - 1 ? 2*L : n - 1; l >= 0; l--) {
            for (unsigned state = 1U << l; state < 2U << l; state++) {
                int r0 = small[n - l - 1][next_state(state, 0)] + end_runs[state][0],
                    r1 = small[n - l - 1][next_state(state, 1)] + end_runs[state][1];
                small[n - l][state] = r0 > r1 ? r0 : r1;
            }
        }

        if (n >= 2) {
            search(n, 1, 0, 2U);
            printf("%d %d ", n, best_runs);
            for (int i = 0; i < n; i++)
                printf("%d", best_a[i]);
            printf("\n");
            fflush(stdout);
            best_runs--;
        }
    }
    return 0;
}

Вихід:

$ gcc -mcmodel=medium -O2 runs.c -o runs
$ ./runs
2 1 00
3 1 000
4 2 0011
5 2 00011
6 3 001001
7 4 0010011
8 5 00110011
9 5 000110011
10 6 0010011001
11 7 00100110011
12 8 001001100100
13 8 0001001100100
14 10 00100110010011
15 10 000100110010011
16 11 0010011001001100
17 12 00100101101001011
18 13 001001100100110011
19 14 0010011001001100100
20 15 00101001011010010100
21 15 000101001011010010100
22 16 0010010100101101001011
23 17 00100101001011010010100
24 18 001001100100110110011011
25 19 0010011001000100110010011
26 20 00101001011010010100101101
27 21 001001010010110100101001011
28 22 0010100101101001010010110100
29 23 00101001011010010100101101011
30 24 001011010010110101101001011010
31 25 0010100101101001010010110100101
32 26 00101001011010010100101101001011
33 27 001010010110100101001011010010100
34 27 0001010010110100101001011010010100
35 28 00100101001011010010100101101001011
36 29 001001010010110100101001011010010100
37 30 0010011001001100100010011001001100100
38 30 00010011001001100100010011001001100100
39 31 001001010010110100101001011010010100100
40 32 0010010100101101001010010110100101001011
41 33 00100110010001001100100110010001001100100
42 35 001010010110100101001011010110100101101011
43 35 0001010010110100101001011010110100101101011
44 36 00101001011001010010110100101001011010010100
45 37 001001010010110100101001011010110100101101011
46 38 0010100101101001010010110100101001011010010100
47 39 00101101001010010110100101101001010010110100101
48 40 001010010110100101001011010010110101101001011010
49 41 0010100101101001010010110100101101001010010110100
50 42 00101001011010010100101101001011010110100101101011
51 43 001010010110100101001011010110100101001011010010100

Тут представлені всі оптимальні послідовності для n ≤ 64 (не лише лексикографічно спочатку), породжені модифікованою версією цієї програми та багатогодинними обчисленнями.

Чудова майже оптимальна послідовність

Префікси нескінченної фрактальної послідовності

1010010110100101001011010010110100101001011010010100101…

що є інваріантним при перетворенні 101 ↦ 10100, 00 ↦ 101:

  101   00  101   101   00  101   00  101   101   00  101   101   00  …
= 10100 101 10100 10100 101 10100 101 10100 10100 101 10100 10100 101 …

здається, має майже майже оптимальне число пробігів - завжди в межах 2 оптимальних для n ≤ 64. Кількість пробігів у перших n символах, поділене на n підходів (13 - 5√5) / 2 ≈ 0,90983. Але виявляється, це не оптимальне співвідношення - дивіться коментарі.


Дякую за відповідь та ваші виправлення. Як ви думаєте, які перспективи полягають у вирішенні силових ситуацій?

1
@ Лембік я не знаю. Я думаю, що моє поточне рішення дещо швидше, ніж o (2 ^ N) дано достатньо пам’яті, але це все ще експоненціально. Я не знайшов прямої формули, яка повністю пропускає процес пошуку, але одна може існувати. Я припускаю , що послідовність ТУЕ-Морса є асимптотично оптимальним з N⋅5 / 6 - O (журнал N) працює, але це , здається, залишається кілька прогонів за фактичної оптимуму.
Anders Kaseorg

Цікаво, що 42/50> 5/6.

1
@ Лембік Завжди слід очікувати, що асимптотичні прогнози іноді битимуться невеликими сумами. Але насправді я абсолютно помилявся - я знайшов набагато кращу послідовність, яка, здається, наближається до N⋅ (13 - 5√5) / 2 ≈ N⋅0,90983 пробігів.
Anders Kaseorg

Дуже вражає. Я думаю, що думка 0,90983 не є правильною. Ознайомтеся з bpaste.net/show/287821dc7214 . Він має довжину 1558 і має 1445 пробігів.

2

Оскільки це не гонка, якщо є лише одна кінь, я подаю своє рішення, хоча це лише частка швидкості Андерса Касеорга і лише третина як криптовалюта. Компілювати з:

gcc -O2 run-count.c -обіг запуску

Основою мого алгоритму є проста зміна та схема XOR:

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

Пробіг нулів у результаті XOR, який більший або рівний поточному періоду / зсуву, вказує на пробіг у початковій рядку для цього періоду. З цього ви можете сказати, як довго був пробіг і де він починається і закінчується. Решта коду накладні, налаштовуючи ситуацію та декодуючи результати.

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

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>

enum { START = 0, WIDTH } ;

// Compare and shuffle just one thing while storing two
typedef union {
    uint16_t token;
    uint8_t data[sizeof(uint16_t)];
} overlay_t;

#define SENTINAL (0)  // marks the end of an array of overlay_t

#define NUMBER_OF_BITS (8 * sizeof(uint64_t))

void period_runs(uint64_t xor_bits, uint8_t nbits, uint8_t period, overlay_t *results) {

    overlay_t *results_ptr = results;
    uint8_t count = 0;

    for (uint8_t position = 0; position < nbits; position++) {

        if (xor_bits & 1ULL) {

            if ((nbits - position) < period) {
                break;  // no room left to succeed further
            }

            if (count >= period) {  // we found a run

                results_ptr->data[START] = position - (count - 1);
                results_ptr->data[WIDTH] = period + count;
                results_ptr++;
            }

            count = 0;
        } else {

            count++;
        }

        xor_bits >>= 1;
    }

    if (count >= period) {  // process the final run, if any

        results_ptr->data[START] = 0;
        results_ptr->data[WIDTH] = period + count;
        results_ptr++;
    }

    results_ptr->token = SENTINAL;
}

void number_runs(uint64_t number, uint8_t bit_length, overlay_t *results) {

    overlay_t sub_results[bit_length];
    uint8_t limit = bit_length / 2 + 1;
    uint64_t mask = (1ULL << (bit_length - 1)) - 1;

    overlay_t *results_ptr = results;
    results_ptr->token = SENTINAL;

    for (uint8_t period = 1; period < limit; period++) {

        uint64_t xor_bits = mask & (number ^ (number >> period));  // heart of the code
        period_runs(xor_bits, bit_length - period, period, sub_results);

        for (size_t i = 0; sub_results[i].token != SENTINAL; i++) {

            bool stop = false;  // combine previous and current results

            for (size_t j = 0; !stop && results[j].token != SENTINAL; j++) {

                // lower period result disqualifies higher period result over the same span 
                stop = (sub_results[i].token == results[j].token);
            }

            if (!stop) {

                (results_ptr++)->token = sub_results[i].token;
                results_ptr->token = SENTINAL;
            }
        }

        mask >>= 1;
    }
}

int main() {

    overlay_t results[NUMBER_OF_BITS];

    for (uint8_t bit_length = 2; bit_length < 25; bit_length++) {

        int best_results = -1;
        uint64_t best_number = 0;

        for (uint64_t number = 1ULL << (bit_length - 1); number < (1ULL << bit_length); number++) {

            // from the discussion comments, I should be able to solve this
            // with just bit strings that begin "11...", so toss the rest
            if ((number & (1ULL << (bit_length - 2))) == 0ULL) {

                continue;
            }

            uint64_t reversed = 0;

            for (uint8_t i = 0; i < bit_length; i++) {

                if (number & (1ULL << i)) {

                    reversed |= (1ULL << ((bit_length - 1) - i));
                }
            }

            if (reversed > number) {

                continue;  // ~ 1/4 of bit_strings are simply reversals, toss 'em
            }

            number_runs(number, bit_length, results);
            overlay_t *results_ptr = results;
            int count = 0;

            while ((results_ptr++)->token != SENTINAL) {

                count++;
            }

            if (count > best_results) {

                best_results = count;
                best_number = number;
            }
        }

        char *best_string = malloc(bit_length + 1);
        uint64_t number = best_number;
        char *string_ptr = best_string;

        for (int i = bit_length - 1; i >= 0; i--) {

            *(string_ptr++) = (number & (1ULL << i)) ? '1' : '0';
        }

        *string_ptr = '\0';

        printf("%u %d %s\n", bit_length, best_results, best_string);

        free(best_string);
    }

    return 0;
}

Вихід:

> gcc -O2 run-count.c -o run-count
> ./run-count
2 1 11
3 1 110
4 2 1100
5 2 11000
6 3 110011
7 4 1100100
8 5 11001100
9 5 110010011
10 6 1100110011
11 7 11001100100
12 8 110110011011
13 8 1100100010011
14 10 11001001100100
15 10 110010011001000
16 11 1100100110010011
17 12 11001100100110011
18 13 110011001001100100
19 14 1101001011010010100
20 15 11010110100101101011
21 15 110010011001001100100
22 16 1100101001011010010100
23 17 11010010110100101001011
24 18 110100101001011010010100
25 19 1100100110010001001100100
26 20 11010010100101101001010010
27 21 110010011001000100110010011
28 22 1101001010010110100101001011

Ласкаво просимо другого коня! Невеликий запит, чому ви та інша відповідь пропонуєте -O2 замість -O3?

@Lembik, з оптимізацією -O2, я міг виміряти різницю у часі за допомогою коду, але я не міг виміряти додаткових параметрів з -O3. Оскільки ми нібито безпечно торгуємося за швидкість, я зрозумів, що найвищий рівень, який насправді змінив, був найкращим. Якщо ви вірите, що мій код стане вище -O3, продовжуйте це!
cdlane

-O3не призначений бути "небезпечним". Це дозволяє автоматичну векторизацію, але тут, мабуть, немає чого векторизувати. Іноді він може робити повільніше код, наприклад, якщо він використовує cmov без гілки для чогось, де галузь прогнозувала б дуже добре. Але зазвичай це повинно допомогти. Зазвичай варто також спробувати clang, щоб побачити, який з gcc або clang робить кращий код для певного циклу. Крім того, це майже завжди допомагає використовувати -march=nativeабо принаймні, -mtune=nativeякщо ви все ще хочете двійкового файлу, який працює в будь-якому місці.
Пітер Кордес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.