Calcola il numero massimo di esecuzioni possibili per una stringa il più grande possibile


24

[Questa domanda è un seguito per calcolare le esecuzioni di una stringa ]

Un periodo pdi una stringa wè un numero intero positivo ptale che w[i]=w[i+p] ogni volta che vengono definiti entrambi i lati di questa equazione. Lasciate che per(w)denotano la dimensione del più piccolo periodo di w. Diciamo che una stringa wè periodica iff per(w) <= |w|/2.

Quindi informalmente una stringa periodica è solo una stringa composta da un'altra stringa ripetuta almeno una volta. L'unica complicazione è che alla fine della stringa non è necessaria una copia completa della stringa ripetuta purché sia ​​ripetuta nella sua interezza almeno una volta.

Ad esempio, considera la stringa x = abcab. per(abcab) = 3come x[1] = x[1+3] = a, x[2]=x[2+3] = be non v'è alcun periodo più piccolo. La stringa non abcabè pertanto periodica. Tuttavia, la stringa ababaè periodica come per(ababa) = 2.

Come altri esempi, abcabca, ababababae abcabcabcsono anche periodiche.

Per coloro a cui piacciono le regex, questa individua se una stringa è periodica o meno:

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

Il compito è trovare tutte le sottostringhe periodiche massime in una stringa più lunga. Questi sono a volte chiamati piste in letteratura.

Una stringa wè una stringa periodica massimale (run) se è periodica e né w[i-1] = w[i-1+p]w[j+1] = w[j+1-p]. Informalmente, la "corsa" non può essere contenuta in una "corsa" più grande con lo stesso periodo.

Poiché due sequenze possono rappresentare la stessa stringa di caratteri che si verificano in punti diversi nella stringa complessiva, rappresenteremo le sequenze per intervalli. Ecco la definizione sopra ripetuta in termini di intervalli.

Una corsa (o sottostringa periodica massima) in una stringa Tè un intervallo [i...j]con j>=itale che

  • T[i...j] è una parola periodica con il punto p = per(T[i...j])
  • È massimo. Formalmente, né T[i-1] = T[i-1+p]T[j+1] = T[j+1-p]. Informalmente, la corsa non può essere contenuta in una corsa più grande con lo stesso periodo.

Indicare dal RUNS(T)set di esecuzioni nella stringa T.

Esempi di piste

  • I quattro sottostringhe periodiche massimi (corse) nella stringa T = atattattsono T[4,5] = tt, T[7,8] = tt, T[1,4] = atat, T[2,8] = tattatt.

  • La stringa T = aabaabaaaacaacaccontiene i seguenti 7 sottostringhe periodiche massimi (corse): 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.

  • La stringa T = atatbatatbcontiene le seguenti tre esecuzioni. Essi sono: T[1, 4] = atat, T[6, 9] = atate T[1, 10] = atatbatatb.

Qui sto usando 1-indicizzazione.

L'obiettivo

Scrivi il codice in modo che per ogni numero intero a partire da 2, produca il maggior numero di esecuzioni contenute in qualsiasi stringa binaria di lunghezza n.

Punto

Il tuo punteggio è il più alto nche raggiungi in 120 secondi in modo tale che per tutti k <= n, nessun altro ha pubblicato una risposta corretta più alta di te. Chiaramente se hai tutte le risposte ottimali otterrai il punteggio per il punteggio più alto nche pubblichi . Tuttavia, anche se la tua risposta non è ottimale, puoi comunque ottenere il punteggio se nessun altro può batterlo.

Lingue e biblioteche

È possibile utilizzare qualsiasi lingua e libreria disponibili. Laddove possibile, sarebbe bene poter eseguire il codice, quindi includere una spiegazione completa su come eseguire / compilare il codice in Linux, se possibile.

Esempio optima

In quanto segue: 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

Cosa dovrebbe esattamente produrre il mio codice?

Per ognuno il ntuo codice dovrebbe generare una singola stringa e il numero di esecuzioni che contiene.

La mia macchina I tempi verranno eseguiti sulla mia macchina. Questa è un'installazione ubuntu standard su un processore a otto core AMD FX-8350. Questo significa anche che devo essere in grado di eseguire il tuo codice.

Risposte principali

  • 49 da Anders Kaseorg in C . A thread singolo ed eseguito con L = 12 (2 GB di RAM).
  • 27 da cdlane in C .


1
Se vuoi solo che consideriamo solo {0,1}-strings, ti preghiamo di dichiararlo esplicitamente. Altrimenti l'alfabeto potrebbe essere infinito e non vedo perché i tuoi test dovrebbero essere ottimali, perché sembra che tu abbia cercato {0,1}anche solo le stringhe.
Flawr,

3
@flawr, ho cercato stringhe su un alfabeto ternario nfino a 12e non ha mai battuto l'alfabeto binario. Dal punto di vista euristico, mi aspetto che una stringa binaria sia ottimale, poiché l'aggiunta di più caratteri aumenta la lunghezza minima di una corsa.
Peter Taylor,

1
Nei risultati ottimali sopra hai "12 7 001001010010" ma il mio codice emette "12 8 110110011011" dove le corse del periodo 1 sono (11, 11, 00, 11, 11), le corse del periodo 3 sono (110110, 011011) e c'è una corsa del periodo 4 (01100110) - dove sbaglio nel conteggio delle corse?
cdlane,

1
@cdlane 0000 ha una corsa. Considera il periodo di 000 ... è sempre 1, non importa quanti zeri.

Risposte:


9

C

Questo fa una ricerca ricorsiva di soluzioni ottimali, fortemente potate usando un limite superiore al numero di esecuzioni che potrebbero essere completate dal resto sconosciuto della stringa. Il calcolo del limite superiore utilizza una gigantesca tabella di ricerca la cui dimensione è controllata dalla costante L( L=11: 0,5 GiB L=12,: 2 GiB L=13,: 8 GiB).

Sul mio laptop, questo aumenta di n = 50 in 100 secondi; la riga successiva arriva a 142 secondi.

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

Produzione:

$ 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

Ecco tutte le sequenze ottimali per n ≤ 64 (non solo la prima lessicografa), generate da una versione modificata di questo programma e da molte ore di calcolo.

Una straordinaria sequenza quasi ottimale

I prefissi dell'infinita sequenza frattale

1010010110100101001011010010110100101001011010010100101…

che è invariante sotto la trasformazione 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 …

sembra avere un numero di esecuzioni quasi ottimale, sempre entro 2 dall'ottimale per n ≤ 64. Il numero di esecuzioni nei primi n caratteri diviso per n avvicinamenti (13 - 5√5) / 2 ≈ 0.90983. Ma si scopre che questo non è il rapporto ottimale — vedi commenti.


Grazie per la risposta e le tue correzioni. Quali pensi siano le prospettive per una soluzione di forza non bruta?

1
@Lembik non lo so. Penso che la mia soluzione attuale sia un po 'più veloce di o (2 ^ N) con memoria sufficiente, ma è ancora esponenziale. Non ho trovato una formula diretta che salti completamente il processo di ricerca, ma uno potrebbe esistere. Immagino che la sequenza Marue -Morse sia asintoticamente ottimale con le corse N /5 / 6 - O (log N), ma sembra rimanere una manciata di corse dietro l'ottimale effettivo.
Anders Kaseorg,

È interessante notare che 42/50> 5/6.

1
@Lembik Uno dovrebbe sempre aspettarsi che le previsioni asintotiche vengano talvolta battute da piccole quantità. Ma in realtà mi sbagliavo completamente: ho trovato una sequenza molto migliore che sembra avvicinarsi a N⋅ (13 - 5√5) / 2 ≈ N⋅0,90983 corse.
Anders Kaseorg,

Molto impressionante. Penso che la congettura di 0.90983 non sia corretta. Dai un'occhiata a bpaste.net/show/287821dc7214 . Ha lunghezza 1558 e ha 1445 piste.

2

Dal momento che non è una gara se c'è un solo cavallo, sto presentando la mia soluzione anche se è solo una frazione della velocità di Anders Kaseorg e solo un terzo criptico. Compilare con:

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

Il cuore del mio algoritmo è un semplice spostamento e uno schema XOR:

inserisci qui la descrizione dell'immagine

Una serie di zeri nel risultato XOR maggiore o uguale al periodo / turno corrente indica una sequenza nella stringa originale per questo periodo. Da questo puoi dire per quanto tempo è stata la corsa e dove inizia e finisce. Il resto del codice è sovraccarico, imposta la situazione e decodifica i risultati.

Spero che farà almeno 28 dopo due minuti sulla macchina di Lembik. (Ho scritto una versione di pthread, ma sono riuscito solo a farlo funzionare ancora più lentamente.)

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

Produzione:

> 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

Benvenuto secondo cavallo! Piccola query, perché tu e l'altra risposta suggerite -O2 invece di -O3?

@Lembik, con l'ottimizzazione di -O2, ho potuto misurare una differenza oraria eseguendo il codice ma non ho potuto misurare alcun ulteriore con -O3. Dato che presumibilmente stiamo commerciando in modo sicuro per la velocità, ho pensato che il livello più alto che effettivamente faceva la differenza fosse il migliore. Se ritieni che il mio codice si posizionerà più in alto con -O3, provalo!
cdlane,

-O3non è destinato a essere "non sicuro". Abilita l'auto-vettorializzazione, ma probabilmente qui non c'è nulla da vettorializzare. A volte può rallentare il codice, ad esempio se utilizza un cmov senza rami per qualcosa in cui un ramo avrebbe previsto molto bene. Ma di solito dovrebbe aiutare. Di solito vale la pena provare anche clang, per vedere quale di gcc o clang rende il codice migliore per un ciclo specifico. Inoltre, aiuta quasi sempre a usare -march=native, o almeno -mtune=nativese vuoi ancora un binario che funzioni ovunque.
Peter Cordes,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.