가능한 한 큰 문자열에 대해 가능한 최대 실행 수 계산


24

[이 질문은 문자열의 런계산 하기위한 후속 조치입니다 ]

마침표 p문자열은 w임의의 양의 정수이다 p되도록 w[i]=w[i+p] 이 방정식의 양쪽이 정의 될 때마다. 하자 per(w)의 작은 기간의 크기를 나타낸다 w. 우리는 문자열 w이주기적인 iff 라고 말합니다 per(w) <= |w|/2.

따라서 비공식적으로주기적인 문자열은 다른 문자열에서 적어도 한 번 반복 된 문자열입니다. 유일한 합병증은 문자열의 끝에 적어도 한 번 전체가 반복되는 한 반복되는 문자열의 전체 사본이 필요하지 않다는 것입니다.

예를 들어 string을 고려하십시오 x = abcab. per(abcab) = 3x[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] = atatT[1, 10] = atatbatatb.

여기서는 1 인덱싱을 사용하고 있습니다.

작업

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 코어 프로세서에 표준 우분투 설치입니다. 이것은 또한 코드를 실행할 수 있어야 함을 의미합니다.

선행 답변

  • C의 Anders Kaseorg에 의한 49 . 단일 스레드 및 L = 12 (2GB RAM)로 실행됩니다.
  • C의 cdlane에 의한 27 .


1
우리가 단지 {0,1}-strings 만을 고려하기를 원한다면, 명시 적으로 언급하십시오. 그렇지 않으면 알파벳이 무한대 일 수 있으며 테스트 케이스가 최적의 이유를 알 수 없습니다 {0,1}. 문자열 만 검색 한 것 같습니다 .
flawr

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의주기를 고려하십시오. 0의 수에 관계없이 항상 1입니다.

답변:


9

기음

이것은 최적의 솔루션을 재귀 적으로 검색하며 알 수없는 나머지 문자열로 완료 될 수있는 실행 수의 상한을 사용하여 정리합니다. 상한 계산은 크기가 상수 L( L=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 …

n ≤ 64에 대해 항상 2의 최적 범위 내에서 거의 최적의 런 수를 갖는 것으로 보입니다 . 첫 n 문자 의 런 수를 n 접근 (13-5√5) / 2 ≈ 0.90983으로 나눈 값입니다. 그러나 이것이 최적의 비율이 아니라는 것이 밝혀졌습니다. 의견을보십시오.


답변과 수정에 감사드립니다. 무차별 포스 솔루션에 대한 전망은 무엇이라고 생각하십니까?

1
@Lembik 몰라요. 내 현재 솔루션이 충분한 메모리가 주어지면 o (2 ^ N)보다 다소 빠르다고 생각하지만 여전히 지수입니다. 검색 프로세스를 완전히 건너 뛰는 직접 수식을 찾지 못했지만 하나는 존재할 수 있습니다. 나는 Thu –Morse 시퀀스 가 N⋅5 / 6-O (log N) 런으로 무증상 적으로 최적 이라고 추측 하지만 실제 최적 값보다 약간의 런을 유지하는 것 같습니다.
Anders Kaseorg

흥미롭게도 42/50> 5/6입니다.

1
@Lembik One은 항상 점근 적 예측이 소량으로 이길 것으로 예상해야합니다. 그러나 실제로 나는 완전히 틀렸다 – 나는 N⋅ (13 − 5√5) / 2 ≈ N⋅0.90983 런에 접근하는 것보다 훨씬 더 나은 서열을 발견했다.
Anders Kaseorg

매우 인상적. 그러나 0.90983 추측은 옳지 않다고 생각합니다. bpaste.net/show/287821dc7214를 확인하십시오 . 길이는 1558이며 실행은 1445입니다.

2

말이 하나뿐이라면 경쟁이 아니기 때문에 Anders Kaseorg의 속도의 일부에 불과하고 암호의 3 분의 1에 불과하지만 솔루션을 제출하고 있습니다. 다음과 같이 컴파일하십시오.

gcc -O2 실행 횟수 .c -o 실행 횟수

내 알고리즘의 핵심은 간단한 시프트 및 XOR 체계입니다.

여기에 이미지 설명을 입력하십시오

XOR 결과에서 현재주기 / 시프트보다 크거나 같은 0의 실행은이 기간의 원래 문자열에서 실행을 나타냅니다. 이것으로부터 당신은 달리는 시간과 시작과 끝을 알 수 있습니다. 나머지 코드는 오버 헤드가 발생하여 상황을 설정하고 결과를 디코딩합니다.

나는 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를 사용하는 경우 코드가 느려질 수 있습니다. 그러나 보통 도움이됩니다. 어떤 gcc 또는 clang이 특정 루프에 대해 더 나은 코드를 만드는지 확인하려면 일반적으로 clang도 시도해 볼 가치가 있습니다. 또한 거의 항상을 사용하는 데 도움이 -march=native되거나 적어도 -mtune=native어디에서나 실행되는 바이너리를 원한다면 도움 이됩니다.
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.