Bir dize için mümkün olan en fazla sayıda koşuyu hesaplayın


24

[Bu soru bir dizgenin işlemlerini hesaplamak için yapılan bir takiptir ]

Bir dönem pbir dize wherhangi bir pozitif tam sayı olduğu pşekilde w[i]=w[i+p] bu denklemin her iki tarafın da tanımlandığı zaman. Let per(w)en küçük döneminin boyutunu göstermektedirler w. Bir dize wperiyodik iff olduğunu söylüyoruz per(w) <= |w|/2.

Dolayısıyla, gayrı resmi olarak bir periyodik dize, sadece en az bir kez tekrarlanan başka bir dizgeden yapılan bir dizedir. Tek karışıklık, dizenin sonunda, en az bir kez bütünüyle tekrar edildiği sürece, tekrarlanan dizinin tam bir kopyasını gerektirmememizdir.

Örneğin, dizeyi düşünün x = abcab. per(abcab) = 3olarak x[1] = x[1+3] = a, x[2]=x[2+3] = bve hiçbir küçük süresi vardır. Bu abcabnedenle dize periyodik değildir. Ancak, dize ababaolarak periyodiktir per(ababa) = 2.

Daha fazla örnek olarak abcabca, ababababave abcabcabcayrıca periyodiktir.

Regexes sevenler için, bu bir dize periyodik olup olmadığını algılar:

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

Görev, tüm maksimum periyodik alt dizileri daha uzun bir dizgede bulmaktır . Bunlara bazen literatürde koşu denir .

Bir alt wdize periyodik ve ne w[i-1] = w[i-1+p]de ne ise, maksimum periyodik alt dizedir (çalıştırma) w[j+1] = w[j+1-p]. Gayriresmi olarak, "run" aynı süre içinde daha büyük bir "run" içeremez.

İki satır, genel dizedeki farklı yerlerde bulunan aynı karakter dizisini temsil edebildiğinden, aralıklarla çalıştırmaları temsil edeceğiz. İşte aralıklarla tekrarlanan yukarıdaki tanım.

String içinde bir çalıştırma (maksimal periyodik substring) Tbir aralık [i...j]ile j>=i, öyle ki

  • T[i...j] periyodik bir periyodik kelimedir p = per(T[i...j])
  • Bu maksimum. Resmen, ne T[i-1] = T[i-1+p]de T[j+1] = T[j+1-p]. Gayrı resmi olarak, koşu aynı dönemdeki daha büyük bir koşuda dahil edilemez.

Dizede RUNS(T)çalıştırma kümesini gösterir T.

Koşu örnekleri

  • Dizede dört maksimal periyodik altdizgelerin (ishal) T = atattattvardır T[4,5] = tt, T[7,8] = tt, T[1,4] = atat, T[2,8] = tattatt.

  • Dize T = aabaabaaaacaacacaşağıdaki 7 maksimal periyodik alt dizeleri (ishal) içerir: 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.

  • Dize T = atatbatatbaşağıdaki üç koşuyu içerir. Bunlar: T[1, 4] = atat, T[6, 9] = atatve T[1, 10] = atatbatatb.

Burada 1 indeksleme kullanıyorum.

Görev

2'den başlayarak her bir tamsayı için, herhangi bir uzunluktaki ikilik dizgide bulunan en büyük sayıdaki çalışmayı çıkarmanız için kod yazınız n.

Gol

Skorunuz n120 saniyede ulaştığınız en yüksek puandır, öyle ki herkes için k <= nsizden daha yüksek bir cevap vermediniz. Açıkça bütün optimum cevaplara sahipseniz, gönderdiğiniz en yüksek notu alırsınız n. Bununla birlikte, cevabınız optimum olmasa bile, başka kimse onu geçemezse puanı alabilirsiniz.

Diller ve kütüphaneler

İstediğiniz dili ve kütüphaneyi istediğiniz gibi kullanabilirsiniz. Mümkünse, kodunuzu çalıştırmanız iyi olur, bu yüzden lütfen mümkünse Linux'ta kodunuzu nasıl çalıştıracağınıza / derleyeceğinize dair tam bir açıklama ekleyin.

Örnek optima

Aşağıda: 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

Kodum tam olarak ne yazmalı?

Her biri niçin kodunuz tek bir dize ve içerdiği çalışma sayısını göstermelidir.

Benim Makine zamanlamaları benim makinede işletilecek. Bu, AMD FX-8350 Sekiz Çekirdekli İşlemciye standart bir ubuntu kurulumudur. Bu ayrıca kodunuzu çalıştırabilmem gerektiği anlamına geliyor.

Ana cevaplar

  • 49 Anders Kaseorg göre C . Tek dişli ve L = 12 (2GB RAM) ile çalıştırın.
  • 27 ° C'de C cdlane .


1
Yalnızca- {0,1}dizeleri düşünmemizi istiyorsanız , lütfen açıkça belirtin. Aksi halde, alfabe sonsuz olabilir ve test kasetlerinin neden optimal olması gerektiğini anlamıyorum, çünkü siz de sadece {0,1}dizgileri araştırdınız .
kusur,

3
@flawr, ben bir üçlü alfabenin üzerinde dizeleri aradı niçin yukarı 12ve ikili alfabe hiç yenemedi. Sezgisel olarak bir ikili dizgenin optimal olmasını beklerdim, çünkü daha fazla karakter eklemek bir koşunun minimum uzunluğunu arttırır.
Peter Taylor

1
Yukarıdaki optimum sonuçlarda, "12 7 001001010010" değerine sahipsiniz, ancak kodum "12 8 110110011011" kodunu veriyor, burada 1. periyodun (11, 11, 00, 11, 11), 3. periyodun (110110, 011011) olduğu ve dönem 4 koşusu (01100110) - Koşu sayımda nerelere yanlış gidiyorum?
cdlane

1
@cdlane 0000 bir koşuya sahip. 000 dönemini düşünün ... kaç sıfır olursa olsun her zaman 1'dir.

Yanıtlar:


9

C

Bu, dizinin bilinmeyen geri kalanı tarafından bitebilecek işlemlerin sayısı üzerinde bir üst sınır kullanılarak yoğun bir şekilde budanmış, optimum çözümler için özyinelemeli bir araştırma yapar. Üst sınır hesaplaması, büyüklüğü sabit tarafından kontrol edilen devasa bir arama tablosu kullanır L( L=11: 0.5 GiB,: L=122 GiB,: L=138 GiB).

Dizüstü bilgisayarımda bu 100 saniyede n = 50'ye kadar çıkıyor ; sonraki satır 142 saniyede geliyor.

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

Çıktı:

$ 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

İşte tüm için optimal dizileri n ≤ 64 Bu programın ve hesaplama saatlerce değiştirilmiş bir versiyonu tarafından üretilen (sadece sözlük sırasında ilk).

Dikkate değer bir neredeyse optimal dizi

Sonsuz fraktal dizinin önekleri

1010010110100101001011010010110100101001011010010100101…

101 ↦ 10100, 00 ↦ 101 dönüşümünde değişmeyen:

  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 …

Her zaman n optimal 64 için optimalin her zaman en iyi 2'si dahilinde - neredeyse n kadar en fazla sayıda koşuya sahip gibi görünüyor. İlk n karakterindeki sayıların n yaklaşımıyla (13 - 5√5) / 2 ≈ 0.90983. Ancak bunun en uygun oran olmadığı anlaşılıyor - yorumlara bakınız.


Cevap ve düzeltmeleriniz için teşekkür ederiz. Beklentilerin kaba olmayan bir güç çözümü için neler olduğunu düşünüyorsun?

1
@ Lembik Bilmiyorum. Mevcut çözümümün yeterli bellek verilen o (2 ^ N) den biraz daha hızlı olduğunu düşünüyorum, ancak yine de üstel. Arama işlemini tamamen atlayan doğrudan bir formül bulamadım, ancak biri olabilir. O varsayım Thue-Morse sekansı olan asimptotik uygun N⋅5 / 6 - O (log N) çalışır, ancak gerçek optimum arkasında çalışan bir avuç kalmak gibi görünüyor.
Anders Kaseorg,

İlginçtir, 42/50> 5/6.

1
@Lembik Kişi, asimptotik tahminlerin her zaman küçük miktarlarda yenilmesini beklemelidir. Fakat aslında tamamen yanlıştım - N⋅ (13 - 5√5) / 2 ≈ N⋅0.90983 koşusuna yaklaşıyor gibi görünen çok daha iyi bir dizi buldum.
Anders Kaseorg,

Çok etkileyici. Bence 0,90983 varsayımı doğru değil. Check bpaste.net/show/287821dc7214 . Uzunluğu 1558 ve 1445 koşusu var.

2

Sadece bir at varsa bu bir yarış olmadığı için, çözümümü sunuyorum ancak Anders Kaseorg'in hızının sadece bir kısmı ve şifreli olarak üçte biri. İle derleyin:

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

Algoritmamın kalbi basit bir değişim ve XOR şemasıdır:

görüntü tanımını buraya girin

XOR sonucundaki bir sıfır akımı, geçerli döneme / vardiyaya eşit veya ondan daha büyük olan, bu dönem için orijinal dizedeki bir çalışmayı belirtir. Bundan koşunun ne kadar sürdüğünü ve nerede başlayıp bittiğini söyleyebilirsiniz. Kodun geri kalan kısmı ek yüke, durumu düzenler ve sonuçların kodunu çözer.

Lembik'in makinesinde iki dakika sonra en az 28 olacağını umuyorum. (Bir pthread sürümünü yazdım, ancak yalnızca daha yavaş çalışmasını sağladım.)

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

Çıktı:

> 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

Hoşgeldin ikinci at! Küçük sorgu, neden siz ve diğer cevabınız -O3 yerine -O2'yi öneriyor?

@ Lembik, -O2 optimizasyonu ile, kodu çalıştıran bir zaman farkını ölçebilirdim ama -O3 ile herhangi bir ek ölçüm yapamadım. Sözde hız için güvenli bir şekilde işlem yaptığımızdan, gerçekten bir fark yaratan en yüksek seviyenin en iyisi olduğunu düşündüm. Kodumun -O3 ile daha üst sıralara çıkacağına inanıyorsanız, bunun için gidin!
cdlane

-O3"güvensiz" olması amaçlanmamıştır. Otomatik vektörleşmeyi mümkün kılar, fakat burada vektörleştirilecek bir şey yoktur. Bazen kodu yavaşlatabilir, örneğin bir dalın çok iyi tahmin edebileceği bir şey için dalsız cmov kullanıyorsa. Ama genellikle yardım etmesi gerekir. Belirli bir döngü için hangi gcc veya clang'ın daha iyi kod yaptığını görmek için genellikle clang'ı denemeye değer. Ayrıca, neredeyse her zaman kullanmanıza yardımcı olur -march=nativeveya en azından -mtune=nativehala herhangi bir yerde çalışan bir ikili isterseniz.
Peter Cordes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.