可能な限り大きな文字列に対して可能な最大実行回数を計算します


24

[この質問は、文字列の実行計算するためのフォローアップです]

期間p文字列はw任意の正の整数であるpようなw[i]=w[i+p] ときはいつでも、この式の両辺が定義されています。per(w)の最小周期のサイズを示しましょうw。文字列wは周期的であると言うper(w) <= |w|/2

したがって、非公式には、定期的な文字列は、少なくとも1回繰り返される別の文字列から構成される単なる文字列です。唯一の問題は、文字列の最後で、少なくとも1回全体が繰り返される限り、繰り返される文字列の完全なコピーを必要としないことです。

たとえば、文字列を考えますx = abcabper(abcab) = 3としてx[1] = x[1+3] = ax[2]=x[2+3] = bより短い期間はありません。abcabしたがって、文字列は周期的ではありません。ただし、文字列ababaはとして周期的per(ababa) = 2です。

より多くの例としては、abcabcaababababaabcabcabcも周期的です。

正規表現が好きな人のために、これは文字列が周期的かどうかを検出します:

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

タスクは、より長い文字列内のすべての最大周期部分文字列を見つけることです。これらは文献ではランと呼ばれることもあります。

部分文字列wは、周期的であり、w[i-1] = w[i-1+p]でもない場合、最大の周期的部分文字列(実行)ですw[j+1] = w[j+1-p]。非公式には、同じ期間のより大きな「実行」に「実行」を含めることはできません。

2回の実行は、文字列全体の異なる場所で発生する同じ文字列を表すことができるため、実行を間隔で表します。上記の定義を間隔で繰り返します。

文字列内のラン(又は最大周期サブストリング)が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

実行の例

  • 文字列の4つの最大の周期ストリング(実行)がT = atattattありT[4,5] = ttT[7,8] = ttT[1,4] = atatT[2,8] = tattatt

  • 文字列はT = aabaabaaaacaacac、以下の7つの最大の定期的なストリング(実行)を含んでいます T[1,2] = aaT[4,5] = aaT[7,10] = aaaaT[12,13] = aaT[13,16] = acacT[1,8] = aabaabaaT[9,15] = aacaaca

  • 文字列にT = atatbatatbは、次の3つの実行が含まれます。彼らは以下の通りです T[1, 4] = atatT[6, 9] = atatT[1, 10] = atatbatatb

ここでは、1-indexingを使用しています。

タスク

2から始まる整数nごとに、lengthの任意のバイナリ文字列に含まれる実行の最大数を出力するようにコードを記述しますn

スコア

あなたのスコアはn120秒で到達する最高値であり、すべてk <= nの人にとって、あなたよりも高い正解を誰も投稿していない。明らかに、すべての最適な回答があれば、n投稿した最高のスコアが得られます。ただし、あなたの答えが最適でなくても、他の誰もそれを打つことができなければ、あなたはまだスコアを得ることができます。

言語とライブラリ

使用可能な任意の言語とライブラリを使用できます。可能であれば、コードを実行できるとよいので、可能な限りLinuxでコードを実行/コンパイルする方法の完全な説明を含めてください。

最適例

以下では: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コードごとに、単一の文字列とそれに含まれる実行回数を出力する必要があります。

私のマシンタイミングは私のマシンで実行されます。これは、AMD FX-8350 8コアプロセッサへの標準のUbuntuインストールです。これは、コードを実行できる必要があることも意味します。

主要な回答

  • 49 Cの Anders Kaseorgによる。シングルスレッドで、L = 12(2GBのRAM)で実行します。
  • Cの cdlaneによる27


1
{0,1}-strings のみを考慮したい場合は、明示的に述べてください。そうしないと、アルファベットが無限になる可能性があり、{0,1}文字列のみを検索したように見えるため、テストケースが最適である理由がわかりません。
-flawr

3
@flawr、私はn最大3文字のアルファベットで文字列を検索しました12が、バイナリアルファベットに勝るものはありませんでした。ヒューリスティックには、文字を追加すると実行の最小長が長くなるため、バイナリ文字列が最適であると予想されます。
ピーターテイラー

1
上記の最適な結果には「12 7 001001010010」がありますが、コードは「12 8 110110011011」を出力します。ここで、期間1の実行は(11、11、00、11、11)、期間3の実行は(110110、011011)であり、期間4の実行(01100110)-実行カウントのどこが間違っていますか?
cdlane

1
@cdlane 0000には1つの実行があります。000の期間を考えてみましょう...ゼロがいくつあっても常に1です。

回答:


9

C

これにより、最適なソリューションの再帰検索が実行され、不明な文字列の残りによって終了する可能性のある実行回数の上限を使用して大幅に削除されます。上限の計算では、サイズが定数LL=11:0.5 GiB 、L=12: 2 GiB 、L=13: 8 GiB)によって制御される巨大なルックアップテーブルを使用します。

私のラップトップでは、これは100秒でn = 50になります。次の行は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 / 2≈0.90983 -近づく(5√513)。しかし、これは最適な比率ではないことがわかりました。コメントを参照してください。


答えと修正をありがとう。見通しはブルートフォース以外のソリューションに対するものだと思いますか?

1
@Lembikわかりません。私の現在の解決策は、十分なメモリがあればo(2 ^ N)よりもやや速いと思いますが、それでも指数関数的です。検索プロセスを完全にスキップする直接的な式は見つかりませんでしたが、存在する可能性があります。私はと推測Thue-Morseのシーケンスがある漸近的に最適なN⋅5/ 6と- O(Nを記録)実行されますが、それは実際の最適背後の実行の一握りを滞在するようです。
アンデルスカセオルグ16

興味深いことに、42/50> 5/6。

1
@Lembik Oneは常に、漸近的予測がわずかな量で打ち負かされることを常に予想する必要があります。しかし、実際、私は完全に間違っていました。N−(13 −5√5)/ 2≈N⋅0.90983ランに近づいているように思えるはるかに良いシーケンスを見つけました。
アンデルスカセオルグ16

非常に印象的。しかし、0.90983の推測は正しくないと思います。bpaste.net/show/287821dc7214をご覧ください。長さは1558で、実行回数は1445です。

2

馬が1頭しかない場合はレースではないので、Anders Kaseorgの速度のほんの数分の1であり、不可解なものの3分の1であるにもかかわらず、解決策を提出します。コンパイル:

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

私のアルゴリズムの中心は、単純なシフトとXORスキームです:

ここに画像の説明を入力してください

現在の期間/シフト以上のXOR結果のゼロの実行は、この期間の元の文字列の実行を示します。これから、実行の長さと開始と終了の場所を知ることができます。残りのコードはオーバーヘッドであり、状況を設定して結果をデコードします。

Lembikのマシンで2分後に少なくとも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

二頭目の馬を歓迎します!小さなクエリ、なぜあなたと他の答えは-O3ではなく-O2を示唆していますか?

@ Lembik、-O2最適化を使用すると、コードの実行時差を測定できましたが、-O3を使用して追加の測定はできませんでした。私たちはおそらく速度と安全にトレードオフしているので、実際に違いを生む最高レベルが最高だと考えました。私のコードが-O3で上位にランクされると思われる場合は、それを試してください!
cdlane

-O3「安全でない」ことを意図していません。これは自動ベクトル化を有効にしますが、おそらくここにはベクトル化するものは何もありません。たとえば、ブランチが非常にうまく予測できる場所にブランチレスcmovを使用する場合など、コードが遅くなることがあります。しかし、通常は役立つはずです。通常、clangを試してみる価値はあります。gccとclangのどちらが特定のループに対してより良いコードを作成するかを確認するためです。また、ほとんどの場合、を使用する-march=nativeと役立ちます。少なくとも-mtune=native、どこでも実行できるバイナリが必要な場合は。
ピーターコーデス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.