Tính số lần chạy tối đa có thể cho một chuỗi càng lớn càng tốt


24

[Câu hỏi này là phần tiếp theo để tính toán các chuỗi chạy ]

Một chu kỳ pcủa một chuỗi wlà bất kỳ số nguyên dương nào psao cho w[i]=w[i+p] bất cứ khi nào cả hai mặt của phương trình này được xác định. Hãy per(w)biểu thị kích thước của khoảng thời gian nhỏ nhất w. Chúng tôi nói rằng một chuỗi wlà iff định kỳ per(w) <= |w|/2.

Vì vậy, không chính thức một chuỗi định kỳ chỉ là một chuỗi được tạo thành từ một chuỗi khác được lặp lại ít nhất một lần. Điều phức tạp duy nhất là ở cuối chuỗi chúng ta không yêu cầu một bản sao đầy đủ của chuỗi lặp lại miễn là nó được lặp lại toàn bộ ít nhất một lần.

Ví dụ, hãy xem xét chuỗi x = abcab. per(abcab) = 3như x[1] = x[1+3] = a, x[2]=x[2+3] = bvà không có thời kỳ nhỏ hơn. Chuỗi abcabdo đó không phải là định kỳ. Tuy nhiên, chuỗi ababalà định kỳ như per(ababa) = 2.

Như nhiều ví dụ abcabca, ababababaabcabcabccũng là định kỳ.

Đối với những người thích regexes, chuỗi này phát hiện xem một chuỗi có định kỳ hay không:

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

Nhiệm vụ là tìm tất cả các chuỗi định kỳ tối đa trong một chuỗi dài hơn. Chúng đôi khi được gọi là chạy trong văn học.

Một chuỗi con wlà một chuỗi con định kỳ tối đa (chạy) nếu nó là định kỳ và w[i-1] = w[i-1+p]cũng không w[j+1] = w[j+1-p]. Một cách không chính thức, "chạy" không thể được chứa trong một "lần chạy" lớn hơn với cùng thời gian.

Vì hai lần chạy có thể biểu thị cùng một chuỗi ký tự xuất hiện ở các vị trí khác nhau trong chuỗi tổng thể, nên chúng tôi sẽ biểu diễn các lần chạy theo các khoảng. Dưới đây là định nghĩa trên lặp đi lặp lại về các khoảng.

Một chuỗi chạy (hoặc chuỗi con định kỳ tối đa) trong một chuỗi Tlà một khoảng [i...j]với j>=i, sao cho

  • T[i...j] là một từ định kỳ với thời kỳ p = per(T[i...j])
  • Nó là tối đa. Chính thức, cũng không T[i-1] = T[i-1+p]phải T[j+1] = T[j+1-p]. Một cách không chính thức, việc chạy không thể được bao gồm trong một lần chạy lớn hơn với cùng thời gian.

Biểu thị bằng RUNS(T)tập chạy trong chuỗi T.

Ví dụ về các hoạt động

  • Bốn chuỗi con kỳ tối đa (chạy) trong chuỗi T = atattattT[4,5] = tt, T[7,8] = tt, T[1,4] = atat, T[2,8] = tattatt.

  • Chuỗi T = aabaabaaaacaacacchứa 7 chuỗi con kỳ tối đa sau (chạy): 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.

  • Chuỗi T = atatbatatbchứa ba lần chạy sau. Đó là: T[1, 4] = atat, T[6, 9] = atatT[1, 10] = atatbatatb.

Ở đây tôi đang sử dụng 1-indexing.

Nhiệm vụ

Viết mã sao cho mỗi số nguyên n bắt đầu từ 2, bạn xuất ra số lần chạy lớn nhất có trong bất kỳ chuỗi nhị phân có độ dài nào n.

Ghi bàn

Điểm của bạn là cao nhất nbạn đạt được trong 120 giây sao cho tất cả k <= n, không ai khác đã đăng câu trả lời đúng cao hơn bạn. Rõ ràng nếu bạn có tất cả các câu trả lời tối ưu thì bạn sẽ nhận được điểm cho bài cao nhất nbạn đăng. Tuy nhiên, ngay cả khi câu trả lời của bạn không phải là tối ưu, bạn vẫn có thể đạt được điểm nếu không ai khác có thể đánh bại nó.

Ngôn ngữ và thư viện

Bạn có thể sử dụng bất kỳ ngôn ngữ và thư viện có sẵn mà bạn thích. Nếu khả thi, sẽ tốt khi có thể chạy mã của bạn, vì vậy vui lòng bao gồm một lời giải thích đầy đủ về cách chạy / biên dịch mã của bạn trong Linux nếu có thể.

Ví dụ tối ưu

Trong các mục sau : 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

Chính xác những gì mã đầu ra của tôi nên?

Đối với mỗi nmã của bạn sẽ xuất ra một chuỗi và số lần chạy mà nó chứa.

Máy của tôi Thời gian sẽ được chạy trên máy của tôi. Đây là bản cài đặt Ubuntu tiêu chuẩn trên Bộ xử lý tám lõi AMD FX-8350. Điều này cũng có nghĩa là tôi cần để có thể chạy mã của bạn.

Câu trả lời hàng đầu

  • 49 bởi Anders Kaseorg trong C . Đơn luồng và chạy với L = 12 (2GB RAM).
  • 27 bởi cdlane trong C .


1
Nếu bạn chỉ muốn chúng tôi xem xét chỉ {0,1}-strings, xin vui lòng nói rõ điều đó. Mặt khác, bảng chữ cái có thể là vô hạn và tôi không hiểu tại sao các mẫu thử của bạn phải tối ưu, vì dường như bạn cũng chỉ tìm kiếm các {0,1}chuỗi.
flawr

3
@flawr, tôi đã tìm kiếm các chuỗi trên một bảng chữ cái ternary cho nđến 12và nó không bao giờ đánh bại bảng chữ cái nhị phân. Về mặt heurist, tôi hy vọng rằng một chuỗi nhị phân sẽ là tối ưu, bởi vì việc thêm nhiều ký tự sẽ làm tăng độ dài tối thiểu của một lần chạy.
Peter Taylor

1
Trong các kết quả tối ưu ở trên, bạn có "12 7 001001010010" nhưng mã của tôi bơm ra "12 8 110110011011" trong đó các giai đoạn 1 là (11, 11, 00, 11, 11), các giai đoạn 3 là (110110, 011011) và đã có một giai đoạn 4 chạy (01100110) - tôi đang sai ở đâu trong lần chạy của tôi?
cdlane

1
@cdlane 0000 có một lần chạy. Hãy xem xét khoảng thời gian 000 ... luôn luôn là 1 cho dù có bao nhiêu số không.

Câu trả lời:


9

C

Điều này thực hiện tìm kiếm đệ quy cho các giải pháp tối ưu, được cắt tỉa cẩn thận bằng cách sử dụng giới hạn trên của số lần chạy có thể được hoàn thành bởi phần còn lại chưa biết của chuỗi. Tính toán giới hạn trên sử dụng bảng tra cứu khổng lồ có kích thước được điều khiển bởi hằng số L( L=11: 0,5 GiB , L=12: 2 GiB , L=13: 8 GiB).

Trên máy tính xách tay của tôi, điều này tăng lên đến n = 50 trong 100 giây; dòng tiếp theo đến ở mức 142 giây.

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

Đầu ra:

$ 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

Dưới đây là tất cả các chuỗi tối ưu cho n ≤ 64 (không chỉ là từ vựng đầu tiên), được tạo bởi một phiên bản sửa đổi của chương trình này và nhiều giờ tính toán.

Một trình tự gần như tối ưu đáng chú ý

Các tiền tố của chuỗi fractal vô hạn

1010010110100101001011010010110100101001011010010100101…

đó là bất biến theo phép biến đổi 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 …

Dường như có số lần chạy gần như tối ưu luôn trong phạm vi 2 tối ưu cho n ≤ 64. Số lần chạy trong n ký tự đầu tiên chia cho n cách tiếp cận (13 - 5√5) / 2 0,9083. Nhưng hóa ra đây không phải là tỷ lệ tối ưu mà xem bình luận.


Cảm ơn bạn câu trả lời và sửa chữa của bạn. Bạn nghĩ gì về triển vọng cho một giải pháp không vũ phu?

1
@Lembik Tôi không biết. Tôi nghĩ rằng giải pháp hiện tại của tôi có phần nhanh hơn o (2 ^ N) khi có đủ bộ nhớ, nhưng nó vẫn theo cấp số nhân. Tôi đã không tìm thấy một công thức trực tiếp bỏ qua hoàn toàn quá trình tìm kiếm, nhưng một công thức có thể tồn tại. Tôi phỏng đoán rằng chuỗi Thue-Morsetiệm cận tối ưu với N⋅5 / 6 - O (log N) chạy, nhưng nó dường như ở một số ít các pha chạy lên phía sau tối ưu thực tế.
Anders Kaseorg

Thật thú vị, 42/50> 5/6.

1
@Lembik Một người nên luôn mong đợi những dự đoán tiệm cận đôi khi bị đánh bại bởi một lượng nhỏ. Nhưng thật ra tôi đã hoàn toàn sai khi tôi tìm thấy một chuỗi tốt hơn nhiều dường như đang tiến gần đến N⋅ (13 - 5√5) / 2 N⋅0.90983.
Anders Kaseorg

Rất ấn tượng. Tôi nghĩ rằng phỏng đoán 0.90983 là không đúng tuy nhiên. Kiểm tra bpaste.net/show/287821dc7214 . Nó có chiều dài 1558 và có 1445 lượt chạy.

2

Vì nó không phải là một cuộc đua nếu chỉ có một con ngựa, tôi đang gửi giải pháp của mình mặc dù nó chỉ bằng một phần tốc độ của Anders Kaseorg và chỉ bằng một phần ba là mật mã. Biên dịch với:

gcc -O2 run-Count.c -o run-Count

Trọng tâm của thuật toán của tôi là một sự thay đổi đơn giản và sơ đồ XOR:

nhập mô tả hình ảnh ở đây

Một số không trong kết quả XOR lớn hơn hoặc bằng khoảng thời gian / ca hiện tại cho thấy một lần chạy trong chuỗi ban đầu cho giai đoạn này. Từ đó bạn có thể biết thời gian chạy là bao lâu và nơi nó bắt đầu và kết thúc. Phần còn lại của mã là chi phí, thiết lập tình huống và giải mã kết quả.

Tôi hy vọng nó sẽ kiếm được ít nhất 28 sau hai phút trên máy của Lembik. (Tôi đã viết một phiên bản pthread, nhưng chỉ quản lý để làm cho nó chạy chậm hơn.)

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

Đầu ra:

> 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

Chào mừng con ngựa thứ hai! Truy vấn nhỏ, tại sao bạn và câu trả lời khác lại gợi ý -O2 thay vì -O3?

@Lembik, với tối ưu hóa -O2, tôi có thể đo chênh lệch thời gian chạy mã nhưng tôi không thể đo bất kỳ bổ sung nào với -O3. Vì chúng tôi được cho là đang giao dịch một cách an toàn cho tốc độ, tôi đã tìm ra mức cao nhất thực sự tạo ra sự khác biệt là tốt nhất. Nếu bạn tin rằng mã của tôi sẽ xếp hạng cao hơn với -O3, hãy chọn nó!
cdlane

-O3không có ý định "không an toàn". Nó cho phép tự động vector hóa, nhưng có lẽ không có gì để vector hóa ở đây. Đôi khi nó có thể làm cho mã chậm hơn, ví dụ nếu nó sử dụng cmov không phân nhánh cho thứ gì đó mà một nhánh sẽ dự đoán rất tốt. Nhưng thường thì nó sẽ giúp. Nó cũng thường đáng để thử clang, để xem gcc hoặc clang nào tạo mã tốt hơn cho một vòng lặp cụ thể. Ngoài ra, nó hầu như luôn luôn giúp sử dụng -march=native, hoặc ít nhất là -mtune=nativenếu bạn vẫn muốn một nhị phân chạy ở bất cứ đâu.
Peter Cordes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.