Tổng các hàm của phép chia với sàng của Erathosthenes


7

Tôi đã gặp vấn đề sau từ một ngân hàng có vấn đề trực tuyến: có tới 105  truy vấn từng yêu cầu để tính tổng

k=LRσ(k)
Ở đâu σ(k) là tổng của ước của k. Nó được cho rằng1LR5106.

Giải pháp của tôi (được mô tả dưới đây) dựa trên sàng của Erathosthenes. Tôi đã triển khai nó trong C ++ và nó hoạt động trong khoảng0.9Trung bình là quá chậm. Tôi biết rằng vấn đề này có thể được giải quyết nhanh hơn ít nhất hai lần nhưng không biết làm thế nào.

Vì vậy, đây là giải pháp của tôi (mảng dựa trên 0):

M = 5 * 1e6
M = array of zeroes of size M + 1
A[1] = 1
for (k = 2; k <= M; k += 1)
    for (j = k; j <= M; j += k)
        A[j] += k

Tôi tính toán trước σ(k) thông qua sàng của Erathosthenes cho mỗi kdưới giá trị tối đa có thể. Khi vòng lặp chính đạt đếnk, A[k] giữ giá trị của σ(k). Sau đó tôi phân công lạiA[k] được i=1kσ(i). Sau khi tiền xử lý như vậy, tất cả các truy vấn có thể được tính trongO(1) thời gian bằng máy tính A[R]A[L1].

Làm thế nào tôi có thể làm cho nó nhanh hơn? Tôi biết hai công thức:

(a)     σ(p1a1psas)=i=1spiai+11pi1
(b)     k=1nσ(k)=k=1nknk

Vấn đề với (a) là việc tính toán nó (ít nhất là trong quá trình thực hiện của tôi) chậm hơn so với ở trên. Vấn đề với (b) là tôi không hiểu cách tính tổng tiền tố với cách tiếp cận như vậy nhanh hơn trongO(n2) thời gian.

Có một thuật toán hiệu quả hơn cho vấn đề này?

(Ngân hàng có vấn đề ghi nhận nguồn gốc của vấn đề là 2012 Kharkov, Trường học mùa đông, Ngày của Serge Kopelovich, Vấn đề H.)


Nếu tôi thực sự hiểu bạn xây dựng Bảng tra cứu lớn và sau đó trả lời các truy vấn, mọi thứ trong thời gian chạy và nút cổ chai đang tính toán Tra cứu? Có hai điều: bạn có thể sắp xếp lại các vòng lặp và phân chia công việc khác nhau không? Nếu có giới hạn về bộ nhớ và thời gian nhưng không phải về kích thước chương trình, bạn có thể tạo một phần của bảng ngoại tuyến không?
Ác

Bạn hiểu đúng. Tôi không biết cách sắp xếp lại các vòng lặp nhưng bây giờ tôi nghĩ rằng rây tuyến tính và tính toán với công thức (a) có thể nhanh hơn.
Igor

Đây có phải là một vấn đề "thế giới thực" hay (dường như) "bị chiếm đoạt", ví dụ như một cuộc thi lập trình hoặc toán học? Có một câu hỏi thực sự ở đây về việc tính toán hiệu quả nhất bảng nhưng ngay cả một cách thực hiện khá đơn giản cũng có thể tính toán toàn bộ kích thước "vừa phải"106bảng chỉ trong vài giây hoặc ít hơn, và sau đó (dường như từ descr) tất cả các truy vấn tiếp theo chỉ là tra cứu bảng O (1). Vì vậy, vấn đề với điều đó là gì? dù sao đi nữa, rõ ràng là trường hợp của nó, không thích một số thiết lập vấn đề ban đầu khi cố gắng làm cho nó nghe giống như một vấn đề trong thế giới thực. lý thuyết số cơ bản được áp dụng ...
vzn

đối với vấn đề đang gặp phải, đề nghị nó không đưa ra các giới hạn hữu hạn trên bàn và thay vào đó được cải cách / tập trung để hỏi về hiệu quả của O (f (n)) của các phương pháp khác nhau, tức là "appoach đơn giản mất thời gian O (f1 (n)), có thể điều này có được cải thiện thành thời gian O (f2 (n)) không? ". dù sao hãy thử Trò chuyện Khoa học Máy tính để phân tích thêm
vzn

@vzn Cảm ơn bạn đã quan tâm đến câu hỏi của tôi. Nguồn gốc của vấn đề được đề cập trong câu hỏi và nó không phải là "thế giới thực". Đó không phải là về tính toán khoa học cực nhanh mà là về các thuật toán đơn giản và hiệu quả vừa phải.
Igor

Câu trả lời:


5

Đây thực sự không phải là khoa học máy tính ...

Bạn tạo một bảng d nơi bạn lưu tổng các ước của k, với k = 1 đến M, trong đó M = 5·106. Đó là phần quan trọng về thời gian. Sau đó, bạn tạo một bảng trong đó bạn lưu tổng các ước cho tất cả 1 ≤ j ≤ k, với k = 1 đến M. Điều đó thật dễ dàng,s0=0, sk+1=sk+dk+1. Và sau đó f (L, R) =sRsL1.

Bảng đầu tiên là vấn đề. Bạn xử lý việc này trongO(nlogn). Và bạn chỉ cần một yếu tố hai, bạn nói ...

Bạn sẽ có một mảng d với 5 triệu mục, có thể là 4 byte cho mỗi mục = 20 Megabyte. Trên một bộ xử lý điển hình mà bạn sẽ có trong máy tính ở nhà, 20 Megabyte không phù hợp với bất kỳ bộ đệm nào. Và mã của bạn thực hiện rất nhiều quyền truy cập vào các phần tử của mảng đó theo thứ tự ngẫu nhiên. Đối với mỗi ước số k tiềm năng, bạn truy cập tất cả các số chia hết cho k và tăng tổng số chia cho k.

Chúng ta hãy làm điều đó với số lượt truy cập ít hơn: Khi bạn truy cập j chia hết cho k, hãy thêm hai ước số k và j / k. Nhưng khi bạn làm điều đó, hãy bắt đầu vớij=k2, chỉ thêm k (vì k = j / k và bạn không muốn đếm số chia hai lần), sau đó thêm k và j / k để biết thêm j. Bạn không cần chia, vì j / k sẽ bằng k + 1, k + 2, k + 3, v.v. Chúng tôi khởi tạo mảng cho trường hợp k = 1, đó là đặt A [j] = 1 + j / 1 cho j 2.

A [1] = 1
for (j = 2; j ≤ M; j += 1)
    A [j] = 1 + j

for (k = 2; k*k ≤ M; k += 1)
    j = k*k
    A [j] += k
    j += k
    s = k + (k + 1)
    while j ≤ M
        A [j] += s
        j += k
        s += 1 // s equals k + j / k

Bạn không lưu hoạt động. Tuy nhiên, hiện bạn đang truy cập vào mảng A theo mô hình thường xuyên hơn nhiều, vì vậy điều này sẽ giúp bạn tiết kiệm thời gian vì việc truy cập vào các mục sẽ nhanh hơn. j sẽ nhỏ hơn, làm cho số lần lặp cho mỗi j lớn hơn, điều này sẽ làm cho dự đoán nhánh hoạt động tốt hơn.

Để cải thiện thêm, bạn sẽ tìm hiểu có bao nhiêu mục mảng phù hợp với bộ đệm của bộ xử lý trong máy tính của bạn, sau đó thực hiện toàn bộ mã cho các phần phụ của mảng (ví dụ: chỉ thay đổi A [0] thành A [99999], sau đó thay đổi A [100000] đến A [199999], v.v.). Theo cách đó, hầu hết các truy cập bộ nhớ sẽ chỉ truy cập bộ nhớ cache, có thể nhanh hơn đáng kể.

Bạn đang thực hiện tra cứu N trong một bảng có kích thước M. Nếu M lớn hơn N, thì có lẽ bạn nên nghĩ về các phương pháp không xây dựng bảng này và có thể chậm hơn rất nhiều cho mỗi lần tra cứu, nhưng tổng thể nhanh hơn do số lượng nhỏ tra cứu. Ngay cả trong trường hợp ở đây có N ≤ 100.000 và M = 5.000.000, bạn có thể không tính các ước số 1, 2, 3, 4, j / 1, j / 2, j / 3, j / 4 trong bảng (điều này làm cho nó nhanh hơn một chút để xây dựng) và xử lý nó trong quá trình tra cứu.

Hoặc bạn có thể thêm tổng các ước số cho các số lẻ, sau đó tính tổng các ước cho các số chẵn (nếu tổng các ước của một số lẻ k là s, thì tổng của 2k là 3 giây, với 4k là 7 giây , với 8k, nó là 15 giây, v.v.), sẽ tiết kiệm gần như một yếu tố 2.

Tái bút Tôi đã đo nó ... làm cho thuật toán đếm tất cả các ước số trở nên thân thiện hơn bằng cách thêm cả j và k / j nhân đôi tốc độ. Tính tổng các ước số cho k lẻ trước, sau đó tính k chẵn từ các giá trị lẻ, làm cho nó nhanh hơn tổng cộng 7 lần. Rõ ràng tất cả chỉ là yếu tố không đổi.


3

Vì vậy, hãy để tôi sắp xếp lại vấn đề của bạn một chút: sử dụng rây nguyên tố sẽ hữu ích, nhưng rây Erathostenes bình thường không đủ tốt.

Những gì bạn cần là sàng nguyên tố làm việc trong thời gian tuyến tính, nhấn mỗi số chỉ một lần.
Một mô tả về sàng nguyên tố thời gian tuyến tính cho thấy cách vượt qua mọi số chỉ một lần.
Lợi ích là gì? Chà, nếu thay vì vượt qua các số chúng ta chèn tổng các ước số vào đó, chúng ta có thuật toán nhanh chóng đặt các ước số (xin nhớ về1 như một ước số).

Ngoài ra còn có một bước bổ sung, các số nguyên tố không được tính toán, vì vậy gặp một bước chúng ta nên viết số chia là số này + 1.

Tiếp theo nên có pass vượt qua (đi qua mảng thêm mục cuối cùng để làm cho nó tổng của tất cả các ước số trước đó).

Bằng cách này, mỗi số phải được viết chính xác một lần, vì vậy điều này chắc chắn tốt hơn so với nỗ lực ban đầu.

Những gì khác có thể được thực hiện?
Vì có ít truy vấn hơn số, tôi nghĩ có lẽ chúng ta có thể tham gia tính toán toàn bộ mảng?

Điều này có thể được thực hiện theo ít nhất hai cách: một cách rõ ràng là tạo một phần (hoặc thậm chí toàn bộ) ngoại tuyến (không phải trong thời gian đo), làm cho chương trình lớn hơn, nhưng không có giới hạn về kích thước.

Một cách khác là tính toán toàn bộ các ước số tích lũy, và sau đó khớp một số hàm lấy lại kết quả từ các chỉ số.

Các hàm có thể hơi phức tạp hoặc để suy nghĩ dễ dàng hơn, chúng ta có thể chia nó thành các phạm vi - làm cho chúng ngắn hơn và dễ tìm hơn.
Sự phức tạp lớn đằng sau đó được thực hiện ngoại tuyến và trong thời gian chạy chỉ truy vấn vấn đề thời gian, vì không có sàng nào cả.


-1

Bạn có thể lưu trữ các kết quả được tính toán trước trong các khoảng {L = 1, R = k * 10 ^ 4} và lực lượng vũ phu chỉ khoảng 2 * 10 ^ 4 số


1
Vấn đề là việc tạo kết quả tính toán trước mất quá nhiều thời gian.
gnasher729

Tại sao đó sẽ là một cách tiếp cận tốt?
Raphael
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.