Máy tính: bạn làm toán


13

Thử thách này một phần là một thách thức thuật toán, liên quan đến một số toán học và một phần đơn giản là một thử thách mã nhanh nhất.

Đối với một số nguyên dương n, hãy xem xét một chuỗi ngẫu nhiên có độ dài 1s và 0s dài nvà gọi nó A. Bây giờ cũng xem xét một chuỗi ngẫu nhiên được chọn thứ hai thống nhất chiều dài ncó giá trị là -1, 0,hay 1và gọi nó B_pre. Bây giờ hãy BB_pre+ B_pre. Đó là B_prekết nối với chính nó.

Bây giờ xem xét các sản phẩm bên trong AB[j,...,j+n-1]và gọi nó Z_jvà chỉ số từ 1.

Bài tập

Đầu ra phải là một danh sách các n+1phân số. Các ithuật ngữ thứ trong đầu ra nên là chính xác khả năng mà tất cả những người đầu tiên ivề Z_jvới j <= ibình đẳng 0.

Ghi bàn

Mã lớn nhất nmà mã của bạn cung cấp đầu ra chính xác trong vòng dưới 10 phút trên máy của tôi.

Tie Breaker

Nếu hai câu trả lời có cùng số điểm, câu trả lời đầu tiên sẽ thắng.

Trong trường hợp (rất rất) không chắc là ai đó tìm được phương pháp để đạt điểm không giới hạn, bằng chứng hợp lệ đầu tiên về giải pháp đó sẽ được chấp nhận.

Dấu

Đừng cố gắng giải quyết vấn đề này một cách toán học, nó quá khó. Cách tốt nhất theo quan điểm của tôi là quay trở lại các định nghĩa cơ bản về xác suất từ ​​trường trung học và tìm ra những cách thông minh để lấy mã để thực hiện một cách liệt kê đầy đủ các khả năng.

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

Bạn có thể sử dụng bất kỳ ngôn ngữ nào có trình biên dịch / trình thông dịch / có sẵn miễn phí. cho Linux và bất kỳ thư viện nào cũng có sẵn miễn phí cho Linux.

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. Do đó, chỉ sử dụng phần mềm miễn phí có sẵn dễ dàng và vui lòng bao gồm các hướng dẫn đầy đủ về cách biên dịch và chạy mã của bạn.


Một số đầu ra thử nghiệm. Chỉ xem xét đầu ra đầu tiên cho mỗi n. Đó là khi i=1. Đối với ntừ 1 đến 13 họ nên được.

 1: 4/6
 2: 18/36
 3: 88/216
 4: 454/1296
 5: 2424/7776
 6: 13236/46656
 7: 73392/279936
 8: 411462/1679616
 9: 2325976/10077696
10: 13233628/60466176
11: 75682512/362797056
12: 434662684/2176782336
13: 2505229744/13060694016

Bạn cũng có thể tìm thấy một công thức chung cho i=1tại http://oeis.org/A081671 .

Bảng xếp hạng (phân chia theo ngôn ngữ)

  • n = 15. Python + trăn song song + pypy trong 1 phút49 của Jakube
  • n = 17. C ++ trong 3 phút37 của Keith Randall
  • n = 16. C ++ trong 2 phút38 bởi Kuroi neko

1
@Knerd Làm sao tôi có thể nói không. Tôi sẽ cố gắng tìm ra cách chạy mã của bạn trong linux nhưng bất kỳ trợ giúp nào cũng được đánh giá cao.

Ok, xin lỗi vì đã xóa bình luận. Đối với tất cả những gì không đọc, đó là nếu F # hoặc C # được cho phép :)
Knerd

Một câu hỏi khác, bạn có thể có một ví dụ về đầu ra đầu vào hợp lệ không?
Knerd

Card đồ họa của bạn là gì? Trông giống như một công việc cho GPU.
Michael M.

1
@Knerd Tôi đã thêm một bảng xác suất cho câu hỏi thay thế. Tôi hi vọng nó hữu ích.

Câu trả lời:


5

C ++, n = 18 trong 9 phút trên 8 chủ đề

(Hãy cho tôi biết nếu nó làm cho nó dưới 10 phút trên máy của bạn.)

Tôi tận dụng một số dạng đối xứng trong mảng B. Đó là các chu kỳ (dịch chuyển theo một vị trí), đảo ngược (đảo ngược thứ tự của các phần tử) và dấu (lấy âm của mỗi phần tử). Đầu tiên tôi tính toán danh sách Bs chúng ta cần thử và trọng lượng của chúng. Sau đó, mỗi B được chạy qua một thói quen nhanh (sử dụng các hướng dẫn bitcount) cho tất cả 2 ^ n giá trị của A.

Đây là kết quả cho n == 18:

> time ./a.out 18
 1: 16547996212044 / 101559956668416
 2:  3120508430672 / 101559956668416
 3:   620923097438 / 101559956668416
 4:   129930911672 / 101559956668416
 5:    28197139994 / 101559956668416
 6:     6609438092 / 101559956668416
 7:     1873841888 / 101559956668416
 8:      813806426 / 101559956668416
 9:      569051084 / 101559956668416
10:      510821156 / 101559956668416
11:      496652384 / 101559956668416
12:      493092812 / 101559956668416
13:      492186008 / 101559956668416
14:      491947940 / 101559956668416
15:      491889008 / 101559956668416
16:      449710584 / 101559956668416
17:      418254922 / 101559956668416
18:      409373626 / 101559956668416

real    8m55.854s
user    67m58.336s
sys 0m5.607s

Biên dịch chương trình dưới đây với g++ --std=c++11 -O3 -mpopcnt dot.cc

#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <thread>
#include <mutex>
#include <chrono>

using namespace std;

typedef long long word;

word n;

void inner(word bpos, word bneg, word w, word *cnt) {
    word maxi = n-1;
    for(word a = (1<<n)-1; a >= 0; a--) {
        word m = a;
        for(word i = maxi; i >= 0; i--, m <<= 1) {
            if(__builtin_popcount(m&bpos) != __builtin_popcount(m&bneg))
                break;
            cnt[i]+=w;
        }
    }
}

word pow(word n, word e) {
    word r = 1;
    for(word i = 0; i < e; i++) r *= n;
    return r;
}

typedef struct {
    word b;
    word weight;
} Bentry;

mutex block;
Bentry *bqueue;
word bhead;
word btail;
word done = -1;

word maxb;

// compute -1*b
word bneg(word b) {
    word w = 1;
    for(word i = 0; i < n; i++, w *= 3) {
        word d = b / w % 3;
        if(d == 1)
            b += w;
        if(d == 2)
            b -= w;
    }
    return b;
}

// rotate b one position
word brot(word b) {
    b *= 3;
    b += b / maxb;
    b %= maxb;
    return b;
}

// reverse b
word brev(word b) {
    word r = 0;
    for(word i = 0; i < n; i++) {
        r *= 3;
        r += b % 3;
        b /= 3;
    }
    return r;
}

// individual thread's work routine
void work(word *cnt) {
    while(true) {
        // get a queue entry to work on
        block.lock();
        if(btail == done) {
            block.unlock();
            return;
        }
        if(bhead == btail) {
            block.unlock();
            this_thread::sleep_for(chrono::microseconds(10));
            continue;
        }
        word i = btail++;
        block.unlock();

        // thread now owns bqueue[i], work on it
        word b = bqueue[i].b;
        word w = 1;
        word bpos = 0;
        word bneg = 0;
        for(word j = 0; j < n; j++, b /= 3) {
            word d = b % 3;
            if(d == 1)
                bpos |= 1 << j;
            if(d == 2)
                bneg |= 1 << j;
        }
        bpos |= bpos << n;
        bneg |= bneg << n;
        inner(bpos, bneg, bqueue[i].weight, cnt);
    }
}

int main(int argc, char *argv[]) {
    n = atoi(argv[1]);

    // allocate work queue
    maxb = pow(3, n);
    bqueue = (Bentry*)(malloc(maxb*sizeof(Bentry)));

    // start worker threads
    word procs = thread::hardware_concurrency();
    vector<thread> threads;
    vector<word*> counts;
    for(word p = 0; p < procs; p++) {
        word *cnt = (word*)calloc(64+n*sizeof(word), 1);
        threads.push_back(thread(work, cnt));
        counts.push_back(cnt);
    }

    // figure out which Bs we actually want to test, and with which weights
    bool *bmark = (bool*)calloc(maxb, 1);
    for(word i = 0; i < maxb; i++) {
        if(bmark[i]) continue;
        word b = i;
        word w = 0;
        for(word j = 0; j < 2; j++) {
            for(word k = 0; k < 2; k++) {
                for(word l = 0; l < n; l++) {
                    if(!bmark[b]) {
                        bmark[b] = true;
                        w++;
                    }
                    b = brot(b);
                }
                b = bneg(b);
            }
            b = brev(b);
        }
        bqueue[bhead].b = i;
        bqueue[bhead].weight = w;
        block.lock();
        bhead++;
        block.unlock();
    }
    block.lock();
    done = bhead;
    block.unlock();

    // add up results from threads
    word *cnt = (word*)calloc(n,sizeof(word));
    for(word p = 0; p < procs; p++) {
        threads[p].join();
        for(int i = 0; i < n; i++) cnt[i] += counts[p][i];
    }
    for(word i = 0; i < n; i++)
        printf("%2lld: %14lld / %14lld\n", i+1, cnt[n-1-i], maxb<<n);
    return 0;
}

Tốt, điều đó giúp tôi không phải làm việc với quái vật cưng của mình nữa ...

Cảm ơn vì điều đó. Bạn có mục chiến thắng hiện tại. Chúng ta phải nhớ -pthreadlại. Tôi nhận được n=17trên máy của tôi.

Rất tiếc .. Bạn nên có tiền thưởng đầy đủ. Xin lỗi tôi đã bỏ lỡ thời hạn.

@Lembik: không vấn đề gì
Keith Randall

6

Python 2 sử dụng pypy và pp: n = 15 trong 3 phút

Cũng chỉ là một lực lượng vũ phu đơn giản. Thật thú vị khi thấy rằng tôi gần như có được tốc độ tương tự như Kuroi neko với C ++. Mã của tôi có thể đạt được n = 12trong khoảng 5 phút. Và tôi chỉ chạy nó trên một lõi ảo.

chỉnh sửa: Giảm không gian tìm kiếm theo hệ số n

Tôi nhận thấy, đó là một vector đạp xe A*của Asản xuất những con số tương tự như xác suất (số giống nhau) là vectơ ban đầu Akhi tôi lặp qua B. Ví dụ: Các vector (1, 1, 0, 1, 0, 0)có các xác suất tương tự như mỗi người trong số các vectơ (1, 0, 1, 0, 0, 1), (0, 1, 0, 0, 1, 1), (1, 0, 0, 1, 1, 0), (0, 0, 1, 1, 0, 1)(0, 1, 1, 0, 1, 0)khi lựa chọn ngẫu nhiên B. Do đó, tôi không phải lặp đi lặp lại trong mỗi 6 vectơ này, nhưng chỉ có khoảng 1 và thay thế count[i] += 1bằng count[i] += cycle_number.

Điều này làm giảm sự phức tạp từ Theta(n) = 6^nđến Theta(n) = 6^n / n. Do đó, n = 13nó nhanh gấp khoảng 13 lần so với phiên bản trước của tôi. Nó tính toán n = 13trong khoảng 2 phút 20 giây. Đối với n = 14nó vẫn còn một chút quá chậm. Mất khoảng 13 phút.

chỉnh sửa 2: Lập trình đa lõi

Không thực sự hài lòng với sự cải tiến tiếp theo. Tôi quyết định cũng cố gắng thực hiện chương trình của mình trên nhiều lõi. Trên lõi 2 + 2 của tôi bây giờ tôi có thể tính toán n = 14trong khoảng 7 phút. Chỉ có một yếu tố của 2 cải thiện.

Mã có sẵn trong repo github này: Liên kết . Các chương trình đa lõi làm cho một chút xấu xí.

chỉnh sửa 3: Giảm không gian tìm kiếm cho Avectơ và Bvectơ

Tôi nhận thấy sự đối xứng gương tương tự cho các vectơ Anhư Kuroi neko đã làm. Vẫn không chắc chắn, tại sao điều này hoạt động (và nếu nó hoạt động cho mỗin ).

Việc giảm không gian tìm kiếm cho Bvectơ thông minh hơn một chút. Tôi đã thay thế thế hệ của vectơ ( itertools.product), bằng một hàm riêng. Về cơ bản, tôi bắt đầu với một danh sách trống và đặt nó vào một ngăn xếp. Cho đến khi ngăn xếp trống, tôi xóa một danh sách, nếu nó không có cùng chiều dài n, tôi tạo 3 danh sách khác (bằng cách nối thêm -1, 0, 1) và đẩy chúng vào ngăn xếp. Tôi một danh sách có cùng độ dài n, tôi có thể đánh giá các khoản tiền.

Bây giờ tôi tự tạo các vectơ, tôi có thể lọc chúng tùy thuộc vào việc tôi có thể đạt tổng = 0 hay không. Ví dụ, nếu vectơ của tôi A(1, 1, 1, 0, 0), và vectơ của tôi Btrông (1, 1, ?, ?, ?), tôi biết, rằng tôi không thể điền vào các ?giá trị, vì vậy A*B = 0. Vì vậy, tôi không phải lặp đi lặp lại trên tất cả 6 vectơ Bcủa biểu mẫu (1, 1, ?, ?, ?).

Chúng ta có thể cải thiện điều này, nếu chúng ta bỏ qua các giá trị cho 1. Như đã lưu ý trong câu hỏi, đối với các giá trị i = 1là chuỗi A081671 . Có nhiều cách để tính toán chúng. Tôi chọn tái phát đơn giản : a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n. Vì i = 1về cơ bản chúng ta có thể tính toán không có thời gian, chúng ta có thể lọc nhiều vectơ hơn B. Ví dụ A = (0, 1, 0, 1, 1)B = (1, -1, ?, ?, ?). Chúng ta có thể bỏ qua các vectơ, trong đó đầu tiên ? = 1, bởi vì A * cycled(B) > 0, cho tất cả các vectơ này. Tôi hy vọng bạn có thể làm theo. Đây có lẽ không phải là ví dụ tốt nhất.

Với điều này tôi có thể tính toán n = 15trong 6 phút.

chỉnh sửa 4:

Nhanh chóng thực hiện ý tưởng tuyệt vời của Kuroi neko, trong đó nói rằng, B-Btạo ra kết quả tương tự. Tăng tốc x2. Thực hiện chỉ là một hack nhanh chóng, mặc dù. n = 15trong 3 phút

Mã số:

Đối với mã hoàn chỉnh, hãy truy cập Github . Các mã sau đây chỉ là một đại diện của các tính năng chính. Tôi đã bỏ nhập khẩu, lập trình đa lõi, in kết quả, ...

count = [0] * n
count[0] = oeis_A081671(n)

#generating all important vector A
visited = set(); todo = dict()
for A in product((0, 1), repeat=n):
    if A not in visited:
        # generate all vectors, which have the same probability
        # mirrored and cycled vectors
        same_probability_set = set()
        for i in range(n):
            tmp = [A[(i+j) % n] for j in range(n)]
            same_probability_set.add(tuple(tmp))
            same_probability_set.add(tuple(tmp[::-1]))
        visited.update(same_probability_set)
        todo[A] = len(same_probability_set)

# for each vector A, create all possible vectors B
stack = []
for A, cycled_count in dict_A.iteritems():
    ones = [sum(A[i:]) for i in range(n)] + [0]
    # + [0], so that later ones[n] doesn't throw a exception
    stack.append(([0] * n, 0, 0, 0, False))

    while stack:
        B, index, sum1, sum2, used_negative = stack.pop()
        if index < n:
            # fill vector B[index] in all possible ways,
            # so that it's still possible to reach 0.
            if used_negative:
                for v in (-1, 0, 1):
                    sum1_new = sum1 + v * A[index]
                    sum2_new = sum2 + v * A[index - 1 if index else n - 1]
                    if abs(sum1_new) <= ones[index+1]:
                        if abs(sum2_new) <= ones[index] - A[n-1]:
                            C = B[:]
                            C[index] = v
                            stack.append((C, index + 1, sum1_new, sum2_new, True))
            else:
                for v in (0, 1):
                    sum1_new = sum1 + v * A[index]
                    sum2_new = sum2 + v * A[index - 1 if index else n - 1]
                    if abs(sum1_new) <= ones[index+1]:
                        if abs(sum2_new) <= ones[index] - A[n-1]:
                            C = B[:]
                            C[index] = v
                            stack.append((C, index + 1, sum1_new, sum2_new, v == 1))
        else:
            # B is complete, calculate the sums
            count[1] += cycled_count  # we know that the sum = 0 for i = 1
            for i in range(2, n):
                sum_prod = 0
                for j in range(n-i):
                    sum_prod += A[j] * B[i+j]
                for j in range(i):
                    sum_prod += A[n-i+j] * B[j]
                if sum_prod:
                    break
                else:
                    if used_negative:
                        count[i] += 2*cycled_count
                    else:
                        count[i] += cycled_count

Sử dụng:

Bạn phải cài đặt pypy (cho Python 2 !!!). Mô-đun python song song không được chuyển cho Python 3. Sau đó, bạn phải cài đặt mô-đun python song song pp-1.6.4.zip . Giải nén nó, cdvào thư mục và gọi pypy setup.py install.

Sau đó, bạn có thể gọi chương trình của tôi với

pypy you-do-the-math.py 15

Nó sẽ tự động xác định số lượng cpu. Có thể có một số thông báo lỗi sau khi kết thúc chương trình, chỉ cần bỏ qua chúng. n = 16nên có thể trên máy của bạn.

Đầu ra:

Calculation for n = 15 took 2:50 minutes

 1  83940771168 / 470184984576  17.85%
 2  17379109692 / 470184984576   3.70%
 3   3805906050 / 470184984576   0.81%
 4    887959110 / 470184984576   0.19%
 5    223260870 / 470184984576   0.05%
 6     67664580 / 470184984576   0.01%
 7     30019950 / 470184984576   0.01%
 8     20720730 / 470184984576   0.00%
 9     18352740 / 470184984576   0.00%
10     17730480 / 470184984576   0.00%
11     17566920 / 470184984576   0.00%
12     17521470 / 470184984576   0.00%
13     17510280 / 470184984576   0.00%
14     17507100 / 470184984576   0.00%
15     17506680 / 470184984576   0.00%

Ghi chú và ý tưởng:

  • Tôi có bộ xử lý i7-4600m với 2 lõi và 4 luồng. Nó không thành vấn đề nếu tôi sử dụng 2 hoặc 4 chủ đề. Việc sử dụng cpu là 50% với 2 luồng và 100% với 4 luồng, nhưng vẫn mất cùng một khoảng thời gian. Tôi không biết tại sao. Tôi đã kiểm tra, rằng mỗi luồng chỉ có một nửa dữ liệu, khi có 4 luồng, kiểm tra kết quả, ...
  • Tôi sử dụng rất nhiều danh sách. Python không hiệu quả trong việc lưu trữ, tôi phải sao chép nhiều danh sách, ... Vì vậy, tôi đã nghĩ đến việc sử dụng một số nguyên thay thế. Tôi có thể sử dụng các bit 00 (cho 0) và 11 (cho 1) trong vectơ A và các bit 10 (cho -1), 00 (cho 0) và 01 (cho 1) trong vectơ B. Đối với sản phẩm của A và B, tôi sẽ chỉ phải tính A & Bvà đếm các khối 01 và 10. Đi xe đạp có thể được thực hiện với việc dịch chuyển vectơ và sử dụng mặt nạ, ... Tôi thực sự đã thực hiện tất cả điều này, bạn có thể tìm thấy nó trong một số cam kết cũ của tôi trên Github. Nhưng hóa ra, để chậm hơn so với danh sách. Tôi đoán, pypy thực sự tối ưu hóa danh sách hoạt động.

Trên PC của tôi, n = 12 chạy mất 7:25 trong khi phần rác C ++ của tôi mất khoảng 1:23, giúp nó nhanh hơn khoảng 5 lần. Chỉ với hai lõi thực, CPU của tôi sẽ đạt được thứ gì đó giống như 2,5 nhân so với ứng dụng đơn luồng, do đó, CPU 8 lõi thực sự sẽ chạy nhanh hơn gấp 3 lần và không tính đến việc cải thiện tốc độ lõi đơn cơ bản hơn i3-2100 già của tôi. Dù việc đi qua tất cả các vòng C ++ này để giải quyết thời gian tính toán tăng theo cấp số nhân thì cũng đáng để nỗ lực gây tranh cãi.

Tôi đang có cảm giác về codegolf.stackexchange.com/questions/41021/ cấp ... Liệu trình tự de Bruijn có hữu ích không?
kennytm

về đa luồng, bạn có thể vắt kiệt thêm một chút lõi 2 + 2 của mình bằng cách khóa từng luồng trên một. Độ lợi x2 là do bộ lập lịch dịch chuyển xung quanh các luồng của bạn mỗi khi di chuyển que diêm trong hệ thống. Với khóa lõi, có lẽ bạn sẽ nhận được mức tăng x2,5 thay thế. Không có ý tưởng nếu Python cho phép thiết lập mối quan hệ bộ xử lý, mặc dù.

Cảm ơn, tôi sẽ xem xét nó. Nhưng tôi khá là một người mới trong đa luồng.
Jakube

nbviewer.ipython.org/gist/minrk/5500077 có một số đề cập về điều này, mặc dù sử dụng một công cụ khác cho song song.

5

bắt nạt len ​​- C ++ - cách quá chậm

Kể từ khi một lập trình viên giỏi hơn đảm nhận việc triển khai C ++, tôi sẽ gọi các lệnh thoát cho cái này.

#include <cstdlib>
#include <cmath>
#include <vector>
#include <bitset>
#include <future>
#include <iostream>
#include <iomanip>

using namespace std;

/*
6^^n events will be generated, so the absolute max
that can be counted by a b bits integer is
E(b*log(2)/log(6)), i.e. n=24 for a 64 bits counter

To enumerate 3 possible values of a size n vector we need
E(n*log(3)/log(2))+1 bits, i.e. 39 bits
*/
typedef unsigned long long Counter; // counts up to 6^^24

typedef unsigned long long Benumerator; // 39 bits
typedef unsigned long      Aenumerator; // 24 bits

#define log2_over_log6 0.3869

#define A_LENGTH ((size_t)(8*sizeof(Counter)*log2_over_log6))
#define B_LENGTH (2*A_LENGTH)

typedef bitset<B_LENGTH> vectorB;

typedef vector<Counter> OccurenceCounters;

// -----------------------------------------------------------------
// multithreading junk for CPUs detection and allocation
// -----------------------------------------------------------------
int number_of_CPUs(void)
{
    int res = thread::hardware_concurrency();
    return res == 0 ? 8 : res;
}

#ifdef __linux__
#include <sched.h>
void lock_on_CPU(int cpu)
{
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(cpu, &mask);
    sched_setaffinity(0, sizeof(mask), &mask);
}
#elif defined (_WIN32)
#include <Windows.h>
#define lock_on_CPU(cpu) SetThreadAffinityMask(GetCurrentThread(), 1 << cpu)
#else
// #warning is not really standard, so this might still cause compiler errors on some platforms. Sorry about that.
#warning "Thread processor affinity settings not supported. Performances might be improved by providing a suitable alternative for your platform"
#define lock_on_CPU(cpu)
#endif

// -----------------------------------------------------------------
// B values generator
// -----------------------------------------------------------------
struct Bvalue {
    vectorB p1;
    vectorB m1;
};

struct Bgenerator {
    int n;                 // A length
    Aenumerator stop;      // computation limit
    Aenumerator zeroes;    // current zeroes pattern
    Aenumerator plusminus; // current +1/-1 pattern
    Aenumerator pm_limit;  // upper bound of +1/-1 pattern

    Bgenerator(int n, Aenumerator start=0, Aenumerator stop=0) : n(n), stop(stop)
    {
        // initialize generator so that first call to next() will generate first value
        zeroes    = start - 1;
        plusminus = -1;
        pm_limit  = 0;
    }

    // compute current B value
    Bvalue value(void)
    {
        Bvalue res;
        Aenumerator pm = plusminus;
        Aenumerator position = 1;
        int i_pm = 0;
        for (int i = 0; i != n; i++)
        {
            if (zeroes & position)
            {
                if (i_pm == 0)  res.p1 |= position; // first non-zero value fixed to +1
                else         
                {
                    if (pm & 1) res.m1 |= position; // next non-zero values
                    else        res.p1 |= position;
                    pm >>= 1;
                }
                i_pm++;
            }
            position <<= 1;
        }
        res.p1 |= (res.p1 << n); // concatenate 2 Bpre instances
        res.m1 |= (res.m1 << n);
        return res;
    }

    // next value
    bool next(void)
    {
        if (++plusminus == pm_limit)
        {
            if (++zeroes == stop) return false;
            plusminus = 0;
            pm_limit = (1 << vectorB(zeroes).count()) >> 1;
        }
        return true;
    }

    // calibration: produces ranges that will yield the approximate same number of B values
    vector<Aenumerator> calibrate(int segments)
    {
        // setup generator for the whole B range
        zeroes = 0;
        stop = 1 << n;
        plusminus = -1;
        pm_limit = 0;

        // divide range into (nearly) equal chunks
        Aenumerator chunk_size = ((Aenumerator)pow (3,n)-1) / 2 / segments;

        // generate bounds for zeroes values
        vector<Aenumerator> res(segments + 1);
        int bound = 0;
        res[bound] = 1;
        Aenumerator count = 0;
        while (next()) if (++count % chunk_size == 0) res[++bound] = zeroes;
        res[bound] = stop;
        return res;
    }
};

// -----------------------------------------------------------------
// equiprobable A values merging
// -----------------------------------------------------------------
static char A_weight[1 << A_LENGTH];
struct Agroup {
    vectorB value;
    int     count;
    Agroup(Aenumerator a = 0, int length = 0) : value(a), count(length) {}
};
static vector<Agroup> A_groups;

Aenumerator reverse(Aenumerator n) // this works on N-1 bits for a N bits word
{
    Aenumerator res = 0;
    if (n != 0) // must have at least one bit set for the rest to work
    {
        // construct left-padded reverse value
        for (int i = 0; i != 8 * sizeof(n)-1; i++)
        {
            res |= (n & 1);
            res <<= 1;
            n >>= 1;
        }

        // shift right to elimitate trailing zeroes
        while (!(res & 1)) res >>= 1;
    }
    return res;
}

void generate_A_groups(int n)
{
    static bitset<1 << A_LENGTH> lookup(0);
    Aenumerator limit_A = (Aenumerator)pow(2, n);
    Aenumerator overflow = 1 << n;
    for (char & w : A_weight) w = 0;

    // gather rotation cycles
    for (Aenumerator a = 0; a != limit_A; a++)
    {
        Aenumerator rotated = a;
        int cycle_length = 0;
        for (int i = 0; i != n; i++)
        {
            // check for new cycles
            if (!lookup[rotated])
            {
                cycle_length++;
                lookup[rotated] = 1;
            }

            // rotate current value
            rotated <<= 1;
            if (rotated & overflow) rotated |= 1;
            rotated &= (overflow - 1);
        }

        // store new cycle
        if (cycle_length > 0) A_weight[a] = cycle_length;
    }

    // merge symetric groups
    for (Aenumerator a = 0; a != limit_A; a++)
    {
        // skip already grouped values
        if (A_weight[a] == 0) continue;

        // regroup a symetric pair
        Aenumerator r = reverse(a);
        if (r != a)
        {
            A_weight[a] += A_weight[r];
            A_weight[r] = 0;
        }  
    }

    // generate groups
    for (Aenumerator a = 0; a != limit_A; a++)
    {
        if (A_weight[a] != 0) A_groups.push_back(Agroup(a, A_weight[a]));
    }
}

// -----------------------------------------------------------------
// worker thread
// -----------------------------------------------------------------
OccurenceCounters solve(int n, int index, Aenumerator Bstart, Aenumerator Bstop)
{
    OccurenceCounters consecutive_zero_Z(n, 0);  // counts occurences of the first i terms of Z being 0

    // lock on assigned CPU
    lock_on_CPU(index);

    // enumerate B vectors
    Bgenerator Bgen(n, Bstart, Bstop);
    while (Bgen.next())
    {
        // get next B value
        Bvalue B = Bgen.value();

        // enumerate A vector groups
        for (const auto & group : A_groups)
        {
            // count consecutive occurences of inner product equal to zero
            vectorB sliding_A(group.value);
            for (int i = 0; i != n; i++)
            {
                if ((sliding_A & B.p1).count() != (sliding_A & B.m1).count()) break;
                consecutive_zero_Z[i] += group.count;
                sliding_A <<= 1;
            }
        }
    }
    return consecutive_zero_Z;
}

// -----------------------------------------------------------------
// main
// -----------------------------------------------------------------
#define die(msg) { cout << msg << endl; exit (-1); }

int main(int argc, char * argv[])
{
    int n = argc == 2 ? atoi(argv[1]) : 16; // arbitray value for debugging
    if (n < 1 || n > 24) die("vectors of lenght between 1 and 24 is all I can (try to) compute, guv");

    auto begin = time(NULL);

    // one worker thread per CPU
    int num_workers = number_of_CPUs();

    // regroup equiprobable A values
    generate_A_groups(n);

    // compute B generation ranges for proper load balancing
    vector<Aenumerator> ranges = Bgenerator(n).calibrate(num_workers);

    // set workers to work
    vector<future<OccurenceCounters>> workers(num_workers);
    for (int i = 0; i != num_workers; i++)
    {
        workers[i] = async(
            launch::async, // without this parameter, C++ will decide whether execution shall be sequential or asynchronous (isn't C++ fun?).
            solve, n, i, ranges[i], ranges[i+1]); 
    }

    // collect results
    OccurenceCounters result(n + 1, 0);
    for (auto& worker : workers)
    {
        OccurenceCounters partial = worker.get();
        for (size_t i = 0; i != partial.size(); i++) result[i] += partial[i]*2; // each result counts for a symetric B pair
    }
    for (Counter & res : result) res += (Counter)1 << n; // add null B vector contribution
    result[n] = result[n - 1];                           // the last two probabilities are equal by construction

    auto duration = time(NULL) - begin;

    // output
    cout << "done in " << duration / 60 << ":" << setw(2) << setfill('0') << duration % 60 << setfill(' ')
        << " by " << num_workers << " worker thread" << ((num_workers > 1) ? "s" : "") << endl;
    Counter events = (Counter)pow(6, n);
    int width = (int)log10(events) + 2;
    cout.precision(5);
    for (int i = 0; i <= n; i++) cout << setw(2) << i << setw(width) << result[i] << " / " << events << " " << fixed << (float)result[i] / events << endl;

    return 0;
}

Xây dựng thực thi

Đây là nguồn C ++ 11 độc lập, biên dịch mà không có cảnh báo và chạy trơn tru trên:

  • Win7 & MSVC2013
  • Win7 & MinGW - g ++ 4.7
  • Ubuntu & g ++ 4.8 (trong VirtualBox VM với 2 CPU được phân bổ)

Nếu bạn biên dịch bằng g ++, hãy sử dụng: g ++ -O3 -pthread -std = c ++ 11 việc
quên đi -pthreadsẽ tạo ra kết xuất lõi tốt và thân thiện.

Tối ưu hóa

  1. Thuật ngữ Z cuối cùng bằng với thuật ngữ đầu tiên (Bpre x A trong cả hai trường hợp), vì vậy hai kết quả cuối cùng luôn luôn bằng nhau, điều này làm cho việc tính toán giá trị Z cuối cùng.
    Lợi ích là không đáng kể, nhưng mã hóa nó không tốn kém gì nên bạn cũng có thể sử dụng nó.

  2. Như Jakube đã phát hiện ra, tất cả các giá trị tuần hoàn của một vectơ A đã cho đều tạo ra xác suất giống nhau.
    Bạn có thể tính toán các giá trị này với một thể hiện duy nhất của A và nhân kết quả với số lần quay có thể có của nó. Các nhóm xoay có thể dễ dàng được tính toán trước trong một khoảng thời gian không đáng kể, vì vậy đây là mức tăng tốc độ mạng rất lớn.
    Vì số lượng hoán vị của vectơ n là n-1, nên độ phức tạp giảm từ o (6 n ) xuống o (6 n / (n-1)), về cơ bản sẽ tiến thêm một bước trong cùng thời gian tính toán.

  3. Nó xuất hiện các cặp mẫu đối xứng cũng tạo ra xác suất tương tự. Ví dụ: 100101 và 101001.
    Tôi không có bằng chứng toán học nào về điều đó, nhưng bằng trực giác khi được trình bày với tất cả các mẫu B có thể, mỗi giá trị A đối xứng sẽ được chia thành giá trị B tương ứng cho cùng một kết quả toàn cầu.
    Điều này cho phép tập hợp lại một số vectơ A khác, để giảm gần 30% số nhóm A.

  4. SAI Đối với một số lý do bán bí ẩn, tất cả các mẫu chỉ có một hoặc hai bit được tạo ra đều có cùng kết quả. Điều này không đại diện cho nhiều nhóm riêng biệt, nhưng chúng vẫn có thể được hợp nhất mà hầu như không mất phí.

  5. Các vectơ B và -B (B với tất cả các thành phần nhân với -1) tạo ra xác suất giống nhau.
    (ví dụ [1, 0, -1, 1] và [-1, 0, 1, -1]).
    Ngoại trừ vectơ null (tất cả các thành phần bằng 0), B và -B tạo thành một cặp vectơ riêng biệt .
    Điều này cho phép cắt giảm một nửa số giá trị B bằng cách chỉ xem xét một trong mỗi cặp và nhân số đóng góp của nó với 2, thêm đóng góp toàn cầu đã biết của null B vào mỗi xác suất chỉ một lần.

Làm thế nào nó hoạt động

Số lượng giá trị B là rất lớn (3 n ), do đó, việc tính toán trước chúng sẽ đòi hỏi lượng bộ nhớ không ổn định, điều này sẽ làm chậm tính toán và cuối cùng làm cạn kiệt RAM có sẵn.
Thật không may, tôi không thể tìm thấy một cách đơn giản để liệt kê một nửa các giá trị B được tối ưu hóa, vì vậy tôi đã dùng đến cách mã hóa một trình tạo chuyên dụng.

Trình tạo B hùng mạnh rất thú vị để viết mã, mặc dù các ngôn ngữ hỗ trợ cơ chế năng suất sẽ cho phép lập trình nó theo cách thanh lịch hơn nhiều.
Những gì nó làm trong một tóm tắt được coi là "bộ xương" của vectơ Bpre như một vectơ nhị phân trong đó 1s đại diện cho các giá trị -1 hoặc +1 thực tế.
Trong số tất cả các giá trị tiềm năng + 1 / -1 này, giá trị đầu tiên được cố định là +1 (do đó chọn một trong các vectơ B / -B có thể) và tất cả các kết hợp + 1 / -1 có thể còn lại được liệt kê.
Cuối cùng, một hệ thống hiệu chuẩn đơn giản đảm bảo mỗi luồng công nhân sẽ xử lý một phạm vi các giá trị có cùng kích thước.

Một giá trị được lọc rất nhiều để tập hợp lại trong các khối có thể trang bị.
Điều này được thực hiện trong giai đoạn tiền điện toán kiểm tra tất cả các giá trị có thể.
Phần này có thời gian thực hiện O (2 n ) không thể bỏ qua và không cần phải tối ưu hóa (mã đã không thể đọc được như vậy!).

Để đánh giá sản phẩm bên trong (chỉ cần kiểm tra bằng 0), các thành phần -1 và 1 của B được tập hợp lại thành các vectơ nhị phân.
Sản phẩm bên trong là null nếu (và chỉ khi) có số lượng + 1s và -1 bằng nhau trong số các giá trị B tương ứng với các giá trị A khác không.
Điều này có thể được tính toán với các thao tác mặt nạ đơn giản và đếm bit, được trợ giúp bởi std::bitsetnó sẽ tạo ra mã đếm bit hiệu quả hợp lý mà không phải dùng đến các hướng dẫn nội tại xấu xí.

Công việc được chia đều cho các lõi, với ái lực CPU bắt buộc (mỗi thứ giúp một chút, hoặc họ nói vậy).

Kết quả ví dụ

C:\Dev\PHP\_StackOverflow\C++\VectorCrunch>release\VectorCrunch.exe 16
done in 8:19 by 4 worker threads
 0  487610895942 / 2821109907456 0.17284
 1   97652126058 / 2821109907456 0.03461
 2   20659337010 / 2821109907456 0.00732
 3    4631534490 / 2821109907456 0.00164
 4    1099762394 / 2821109907456 0.00039
 5     302001914 / 2821109907456 0.00011
 6     115084858 / 2821109907456 0.00004
 7      70235786 / 2821109907456 0.00002
 8      59121706 / 2821109907456 0.00002
 9      56384426 / 2821109907456 0.00002
10      55686922 / 2821109907456 0.00002
11      55508202 / 2821109907456 0.00002
12      55461994 / 2821109907456 0.00002
13      55451146 / 2821109907456 0.00002
14      55449098 / 2821109907456 0.00002
15      55449002 / 2821109907456 0.00002
16      55449002 / 2821109907456 0.00002

Biểu diễn

Đa luồng nên hoạt động hoàn hảo trên điều này, mặc dù chỉ các lõi "thật" mới đóng góp hoàn toàn vào tốc độ tính toán. CPU của tôi chỉ có 2 lõi cho 4 CPU và mức tăng so với phiên bản đơn luồng là "chỉ" khoảng 3,5.

Trình biên dịch

Một vấn đề ban đầu với đa luồng đã khiến tôi tin rằng các trình biên dịch GNU đang hoạt động kém hơn Microsoft.

Sau khi kiểm tra kỹ lưỡng hơn, có vẻ như g ++ đã chiến thắng một lần nữa, tạo ra mã nhanh hơn xấp xỉ 30% (tỷ lệ tương tự tôi nhận thấy trên hai dự án nặng tính toán khác).

Đáng chú ý, std::bitsetthư viện được triển khai với các hướng dẫn đếm bit chuyên dụng theo g ++ 4.8, trong khi MSVC 2013 chỉ sử dụng các vòng lặp của dịch chuyển bit thông thường.

Như người ta có thể mong đợi, việc biên dịch thành 32 hoặc 64 bit không tạo ra sự khác biệt.

Tinh chỉnh thêm

Tôi nhận thấy một vài nhóm A tạo ra xác suất giống nhau sau tất cả các hoạt động giảm, nhưng tôi không thể xác định một mẫu có thể cho phép tập hợp lại chúng.

Dưới đây là các cặp tôi nhận thấy cho n = 11:

  10001011 and 10001101
 100101011 and 100110101
 100101111 and 100111101
 100110111 and 100111011
 101001011 and 101001101
 101011011 and 101101011
 101100111 and 110100111
1010110111 and 1010111011
1011011111 and 1011111011
1011101111 and 1011110111

Tôi nghĩ rằng hai xác suất cuối cùng phải luôn giống nhau. Điều này là do sản phẩm bên trong thứ n + 1 thực sự giống với sản phẩm đầu tiên.

Điều tôi muốn nói là n sản phẩm bên trong đầu tiên bằng 0 khi và chỉ khi n + 1 đầu tiên là. Sản phẩm bên trong cuối cùng không cung cấp bất kỳ thông tin mới nào như bạn đã thực hiện trước đó. Vì vậy, số chuỗi cung cấp cho n sản phẩm 0 hoàn toàn giống với số chuỗi cung cấp cho n + 1 sản phẩm không.

Thay vì quan tâm, chính xác thì bạn đã tính toán cái gì?

Cảm ơn đã cập nhật nhưng tôi không hiểu dòng "0 2160009216 2176782336". Chính xác thì bạn đang tính gì trong trường hợp này? Xác suất mà sản phẩm bên trong đầu tiên bằng 0 nhỏ hơn nhiều.

Bạn có thể cho một số lời khuyên về cách biên dịch và chạy này? Tôi đã thử g ++ -Wall -std = c ++ 11 kuroineko.cpp -o kuroineko và ./kuroineko 12 nhưng nó mang lạiterminate called after throwing an instance of 'std::system_error' what(): Unknown error -1 Aborted (core dumped)
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.