Oblicz maksymalną możliwą liczbę przebiegów dla tak dużego ciągu, jak to możliwe

24

[To pytanie jest kontynuacją do obliczenia ciągów ]

Okres płańcucha wjest dowolną liczbą całkowitą dodatnią, ptaką, że w[i]=w[i+p] za każdym razem , gdy zdefiniowane są obie strony tego równania. Niech per(w)oznacza rozmiar najmniejszego okresu w. Mówimy, że ciąg wjest okresowy iff per(w) <= |w|/2.

Tak więc nieformalnie ciąg okresowy jest po prostu ciągiem, który składa się z innego ciągu powtórzonego co najmniej raz. Jedyną komplikacją jest to, że na końcu łańcucha nie wymagamy pełnej kopii powtarzanego ciągu, o ile jest on powtarzany w całości przynajmniej raz.

Na przykład rozważ ciąg x = abcab. per(abcab) = 3jak x[1] = x[1+3] = a, x[2]=x[2+3] = bi nie ma mniejszy okres. Łańcuch nie abcabjest zatem okresowy. Jednak ciąg ababajest okresowy jak per(ababa) = 2.

Jako więcej przykładów abcabca, ababababai abcabcabcsą również okresowe.

Dla tych, którzy lubią wyrażenia regularne, ten wykrywa, czy ciąg jest okresowy, czy nie:

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

Zadaniem jest znalezienie wszystkich maksymalnych okresowych podciągów w dłuższym ciągu. Są one czasami nazywane działa w literaturze.

Podciąg wjest maksymalnym podciągiem okresowym (uruchomionym), jeśli jest okresowy i ani w[i-1] = w[i-1+p]ani w[j+1] = w[j+1-p]. Nieoficjalnie „przebieg” nie może być zawarty w większym „przebiegu” z tym samym okresem.

Ponieważ dwa przebiegi mogą reprezentować ten sam ciąg znaków występujących w różnych miejscach całego ciągu, będziemy reprezentować przebiegi według przedziałów. Oto powyższa definicja powtarzana w kategoriach odstępów.

Przebieg (lub maksymalny okresowy podłańcuch) w ciągu Tjest interwałem [i...j]z j>=itakim, że

  • T[i...j] to okresowe słowo z kropką p = per(T[i...j])
  • Jest maksymalny. Formalnie ani T[i-1] = T[i-1+p]ani T[j+1] = T[j+1-p]. Nieoficjalnie przebieg nie może być zawarty w większym przebiegu z tym samym okresem.

Oznacz przez RUNS(T)zestaw przebiegów w ciągu T.

Przykłady przebiegów

  • Cztery maksymalne okresowe podciągi (uruchamia) w ciągu znaków T = atattattT[4,5] = tt, T[7,8] = tt, T[1,4] = atat, T[2,8] = tattatt.

  • Łańcuch T = aabaabaaaacaacaczawiera 7 następujących maksymalnych okresowych podciągi (uruchamia): 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.

  • Ciąg T = atatbatatbzawiera następujące trzy przebiegi. Są to: T[1, 4] = atat, T[6, 9] = atati T[1, 10] = atatbatatb.

Tutaj używam 1-indeksowania.

Zadanie

Napisz kod, aby dla każdej liczby całkowitej n rozpoczynającej się od 2 wypisałeś największą liczbę przebiegów zawartych w dowolnym ciągu binarnym o długości n.

Wynik

Twój wynik jest najwyższy, njaki osiągniesz w 120 sekund, dzięki czemu k <= nnikt inny nie opublikował lepszej poprawnej odpowiedzi niż ty. Oczywiście, jeśli masz wszystkie optymalne odpowiedzi, otrzymasz wynik za najwyższą, nktórą opublikujesz. Jednak nawet jeśli twoja odpowiedź nie jest optymalna, nadal możesz uzyskać wynik, jeśli nikt inny go nie pokona.

Języki i biblioteki

Możesz użyć dowolnego dostępnego języka i bibliotek, które ci się podobają. Tam, gdzie jest to wykonalne, dobrze byłoby móc uruchomić kod, więc proszę podać pełne wyjaśnienie, jak uruchomić / skompilować kod w systemie Linux, jeśli to w ogóle możliwe.

Przykładowe optima

W następujących przypadkach: 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

Co dokładnie powinien wypisać mój kod?

Dla każdego nkodu należy wypisać pojedynczy ciąg znaków i liczbę zawartych w nim uruchomień.

Moja maszyna Czasy zostaną uruchomione na moim komputerze. Jest to standardowa instalacja ubuntu na ośmiordzeniowym procesorze AMD FX-8350. Oznacza to również, że muszę być w stanie uruchomić Twój kod.

Wiodące odpowiedzi

  • 49 Anders Kaseorg w C . Jednowątkowy i działający z L = 12 (2 GB pamięci RAM).
  • 27 przez cdlane w C .
Społeczność
źródło
1
Jeśli chcesz, abyśmy rozważali tylko {0,1}-ciągi, wyraźnie to zaznacz. W przeciwnym razie alfabet może być nieskończony i nie rozumiem, dlaczego twoje przypadki testowe powinny być optymalne, ponieważ wygląda na to, że przeszukałeś tylko {0,1}łańcuchy.
flawr
3
@flawr, szukałem ciągi nad potrójnym alfabetu dla npoprzedzającym 12i nigdy nie pokazał się alfabetu binarnego. Heurystycznie spodziewałbym się, że ciąg binarny powinien być optymalny, ponieważ dodanie większej liczby znaków zwiększa minimalną długość przebiegu.
Peter Taylor
1
W powyższych optymalnych wynikach masz „12 7 001001010010”, ale mój kod wypompowuje „12 8 110110011011”, gdzie przebiega okres 1 (11, 11, 00, 11, 11), przebiega okres 3 (110110, 011011) i jest bieg okresu 4 (01100110) - gdzie popełniam błąd licząc przebieg?
cdlane
1
@cdlane 0000 ma jeden przebieg. Weźmy pod uwagę okres 000 ... to zawsze 1 bez względu na to, ile zer.

Odpowiedzi:

9

do

Odbywa się to rekurencyjnie w poszukiwaniu optymalnych rozwiązań, mocno przycinanych przy użyciu górnej granicy liczby przebiegów, które mogłyby zostać zakończone przez nieznaną pozostałą część łańcucha. Obliczenia w górnej granicy używają gigantycznej tabeli odnośników, której rozmiar jest kontrolowany przez stałą L( L=11: 0,5 GiB,: L=122 GiB,: L=138 GiB).

Na moim laptopie zwiększa się o n = 50 w 100 sekund; następna linia przychodzi za 142 sekundy.

#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;
}

Wydajność:

$ 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

Oto wszystkie optymalne sekwencje dla n ≤ 64 (nie tylko leksykograficznie pierwsze), wygenerowane przez zmodyfikowaną wersję tego programu i wiele godzin obliczeń.

Niezwykła prawie optymalna sekwencja

Przedrostki nieskończonej sekwencji fraktalnej

1010010110100101001011010010110100101001011010010100101…

który jest niezmienny w przypadku transformacji 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 …

wydają się mieć bardzo prawie optymalną liczbę przebiegów - zawsze w granicach 2 optymalnych dla n ≤ 64. Liczba przebiegów w pierwszych n znakach podzielona przez n podejść (13 - 5√5) / 2 ≈ 0,90983. Ale okazuje się, że nie jest to optymalny stosunek - patrz komentarze.

Anders Kaseorg
źródło
Dziękuję za odpowiedź i twoje poprawki. Jak myślisz, jakie są perspektywy rozwiązania nie brutalnego?
1
@Lembik Nie wiem. Myślę, że moje obecne rozwiązanie jest nieco szybsze niż o (2 ^ N), biorąc pod uwagę wystarczającą ilość pamięci, ale wciąż jest wykładnicze. Nie znalazłem bezpośredniej formuły, która całkowicie pomija proces wyszukiwania, ale może istnieć. Przypuszczam, że sekwencja Thue – Morse'a jest asymptotycznie optymalna dla przebiegów N⋅5 / 6 - O (log N), ale wydaje się, że pozostaje kilka garści przebiegów za rzeczywistym optymalnym.
Anders Kaseorg
Co ciekawe, 42/50> 5/6.
1
@Lembik Zawsze należy spodziewać się, że prognozy asymptotyczne będą czasem pokonane małymi kwotami. Ale tak naprawdę całkowicie się myliłem - znalazłem znacznie lepszą sekwencję, która wydaje się zbliżać do N⋅ (13 - 5√5) / 2 ≈ N⋅0.90983 przebiegów.
Anders Kaseorg
Bardzo imponujące. Myślę jednak, że hipoteza 0.90983 jest nieprawidłowa. Sprawdź bpaste.net/show/287821dc7214 . Ma długość 1558 i 1445 przebiegów.
2

Ponieważ nie jest to wyścig, jeśli jest tylko jeden koń, przedstawiam moje rozwiązanie, choć jest to tylko ułamek prędkości Andersa Kaseorga i tylko jedna trzecia jako tajemnicza. Połącz z:

gcc -O2 run-count.c -o run-count

Sercem mojego algorytmu jest prosta zmiana i schemat XOR:

wprowadź opis zdjęcia tutaj

Ciąg zer w wyniku XOR, który jest większy lub równy bieżącemu okresowi / zmianie, wskazuje bieg w oryginalnym ciągu dla tego okresu. Na podstawie tego możesz określić, jak długi był bieg i gdzie zaczyna się i kończy. Reszta kodu jest narzutem, konfigurowaniem sytuacji i dekodowaniem wyników.

Mam nadzieję, że po dwóch minutach na maszynie Lembika przyniesie co najmniej 28. (Napisałem wersję pthread, ale udało mi się ją uruchomić jeszcze wolniej).

#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;
}

Wydajność:

> 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
cdlane
źródło
Witamy drugiego konia! Małe zapytanie, dlaczego ty i druga odpowiedź sugerujecie -O2 zamiast -O3?
@Lembik, z optymalizacją -O2, mogłem zmierzyć różnicę czasu przy uruchamianiu kodu, ale nie mogłem zmierzyć żadnego dodatkowego z -O3. Ponieważ przypuszczamy, że bezpiecznie handlujemy szybkością, uznałem, że najwyższy poziom, który faktycznie zrobił różnicę, był najlepszy. Jeśli uważasz, że mój kod zajmie wyższą pozycję z -O3, idź do niego!
cdlane
-O3nie ma być „niebezpieczne”. Umożliwia automatyczną wektoryzację, ale prawdopodobnie nie ma tutaj wektoryzacji. Czasami może spowolnić kod, np. Jeśli używa bezgałęziowego cmov do czegoś, co gałąź bardzo dobrze przewidziała. Ale zwykle powinno to pomóc. Zazwyczaj warto też spróbować clang, aby zobaczyć, które z gcc lub clang tworzą lepszy kod dla określonej pętli. Ponadto prawie zawsze pomaga w użyciu -march=native, a przynajmniej -mtune=nativejeśli chcesz, aby plik binarny działał w dowolnym miejscu.
Peter Cordes