Tính ngẫu nhiên tùy ý (Phiên bản tốc độ)


10

Số nguyên đã cho n, tính một tập hợp các nsố nguyên duy nhất ngẫu nhiên trong phạm vi 1..n^2(đã bao gồm) sao cho tổng của tập hợp bằngn^2

Ngẫu nhiên, trong trường hợp này, có nghĩa là ngẫu nhiên thống nhất giữa các đầu ra hợp lệ. Mỗi đầu ra hợp lệ cho một nhất định nphải có cơ hội thống nhất được tạo ra.

Ví dụ, n=3nên có một cơ hội 1/3 mỗi xuất ra 6, 1, 2, 3, 5, 1hoặc 4, 3, 2. Vì đây là một bộ, thứ tự là không liên quan, 4, 3, 2giống hệt với3, 2, 4

Chấm điểm

Người chiến thắng là chương trình có thể tính toán cao nhất ntrong vòng dưới 60 giây.
Lưu ý: Để ngăn chặn mã hóa một phần có thể, tất cả các mục phải dưới 4000 byte

Kiểm tra

Tất cả mã sẽ được chạy trên máy Windows 10 cục bộ của tôi (Razer Blade 15, RAM 16 GB, lõi Intel i7-8750H 6, 4.1GHz, GTX 1060 trong trường hợp bạn muốn lạm dụng GPU), vì vậy vui lòng cung cấp hướng dẫn chi tiết để chạy mã của bạn máy của tôi
Theo yêu cầu, các mục nhập có thể được chạy qua Debian trên WSL hoặc trên Máy ảo Xubfox (cả hai trên cùng một máy như trên)

Bài dự thi sẽ được chạy liên tiếp 50 lần, điểm cuối cùng sẽ là trung bình của tất cả 50 kết quả.



Mã hóa có được phép một chút không, nếu nó dưới 4000 byte?
Quintec

@Quintec Không, mã hóa cứng là một lỗ hổng tiêu chuẩn, do đó bị cấm theo mặc định. Điều khó khăn là mã hóa cứng cũng được coi là một tiêu chí không thể quan sát được, vì vậy tôi không thể chính thức nói "Không mã hóa" ngoài những gì lỗ hổng không tuân theo. Do đó giới hạn byte. Nói cách khác: Vui lòng không mã hóa cứng
Skidsdev

1
Hầu hết các bài nộp sẽ sử dụng phương pháp từ chối và do đó thời gian chạy sẽ là ngẫu nhiên và có độ biến động lớn. Điều đó làm cho thời gian trở nên khó khăn
Luis Mendo

2
Ồ, tôi quên mất - vì một số giải pháp có thể quyết định sử dụng RNG chất lượng thấp để nhanh chóng, nên có thể cần phải cung cấp thói quen hộp đen lấy n và tạo ra một số ngẫu nhiên trong (1..n) và buộc tất cả giải pháp để sử dụng nó.
dùng202729

Câu trả lời:


6

Rỉ sét , n ≈ 1400

Cách chạy

Xây dựng với cargo build --release, và chạy với target/release/arbitrary-randomness n.

Chương trình này chạy nhanh nhất với nhiều bộ nhớ (tất nhiên miễn là không hoán đổi). Bạn có thể điều chỉnh mức sử dụng bộ nhớ của nó bằng cách chỉnh sửa MAX_BYTEShằng số, hiện được đặt ở mức 8 GiB.

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

Tập hợp được xây dựng theo một chuỗi các quyết định nhị phân (mỗi số nằm trong hoặc ngoài tập hợp), mỗi xác suất được tính toán kết hợp bằng cách đếm số lượng tập hợp có thể xây dựng sau mỗi lựa chọn bằng lập trình động.

Việc sử dụng bộ nhớ cho n lớn được giảm bằng cách sử dụng một phiên bản của chiến lược phân vùng nhị thức này .

Cargo.toml

[package]
name = "arbitrary-randomness"
version = "0.1.0"
authors = ["Anders Kaseorg <andersk@mit.edu>"]

[dependencies]
rand = "0.6"

src/main.rs

extern crate rand;

use rand::prelude::*;
use std::env;
use std::f64;
use std::mem;

const MAX_BYTES: usize = 8 << 30; // 8 gibibytes

fn ln_add_exp(a: f64, b: f64) -> f64 {
    if a > b {
        (b - a).exp().ln_1p() + a
    } else {
        (a - b).exp().ln_1p() + b
    }
}

fn split(steps: usize, memory: usize) -> usize {
    if steps == 1 {
        return 0;
    }
    let mut u0 = 0;
    let mut n0 = f64::INFINITY;
    let mut u1 = steps;
    let mut n1 = -f64::INFINITY;
    while u1 - u0 > 1 {
        let u = (u0 + u1) / 2;
        let k = (memory * steps) as f64 / u as f64;
        let n = (0..memory)
            .map(|i| (k - i as f64) / (i as f64 + 1.))
            .product();
        if n > steps as f64 {
            u0 = u;
            n0 = n;
        } else {
            u1 = u;
            n1 = n;
        }
    }
    if n0 - (steps as f64) <= steps as f64 - n1 {
        u0
    } else {
        u1
    }
}

fn gen(n: usize, rng: &mut impl Rng) -> Vec<usize> {
    let s = n * n.wrapping_sub(1) / 2;
    let width = n.min(MAX_BYTES / ((s + 1) * mem::size_of::<f64>()));
    let ix = |m: usize, k: usize| m + k * (s + 1);
    let mut ln_count = vec![-f64::INFINITY; ix(0, width)];
    let mut checkpoints = Vec::with_capacity(width);
    let mut a = Vec::with_capacity(n);
    let mut m = s;
    let mut x = 1;

    for k in (1..=n).rev() {
        let i = loop {
            let i = checkpoints.len();
            let k0 = *checkpoints.last().unwrap_or(&0);
            if k0 == k {
                checkpoints.pop();
                break i - 1;
            }
            if i == 0 {
                ln_count[ix(0, i)] = 0.;
                for m in 1..=s {
                    ln_count[ix(m, i)] = -f64::INFINITY;
                }
            } else {
                for m in 0..=s {
                    ln_count[ix(m, i)] = ln_count[ix(m, i - 1)];
                }
            }
            let k1 = k - split(k - k0, width - 1 - i);
            for step in k0 + 1..=k1 {
                for m in step..=s {
                    ln_count[ix(m, i)] = ln_add_exp(ln_count[ix(m - step, i)], ln_count[ix(m, i)]);
                }
            }
            if k1 == k {
                break i;
            }
            checkpoints.push(k1);
        };

        while m >= k && rng.gen_bool((ln_count[ix(m - k, i)] - ln_count[ix(m, i)]).exp()) {
            m -= k;
            x += 1;
        }
        a.push(x);
        x += 1;
    }
    a
}

fn main() {
    if let [_, n] = &env::args().collect::<Vec<_>>()[..] {
        let n = n.parse().unwrap();
        let mut rng = StdRng::from_entropy();
        println!("{:?}", gen(n, &mut rng));
    } else {
        panic!("expected one argument");
    }
}

Hãy thử trực tuyến!

(Lưu ý: phiên bản TIO có một vài sửa đổi. Đầu tiên, giới hạn bộ nhớ giảm xuống còn 1 GiB. Thứ hai, vì TIO không cho phép bạn viết Cargo.tomlvà phụ thuộc vào các thùng bên ngoài như rand, thay vào đó tôi đã kéo vào drand48từ thư viện C bằng cách sử dụng FFI. Tôi không bận tâm đến việc gieo hạt giống, vì vậy phiên bản TIO sẽ tạo ra kết quả tương tự trên mỗi lần chạy. Đừng sử dụng phiên bản TIO để đo điểm chuẩn chính thức.)


Vì định dạng dấu phẩy động là hữu hạn, có thể tối ưu hóa ln_add_expbằng cách kiểm tra xem chênh lệch tuyệt đối có lớn hơn ~ 15 hay không, có thể nhanh hơn nếu có nhiều bổ sung như vậy.
dùng202729

@ user202729 Không, gần như tất cả các ln_add_expcuộc gọi liên quan đến đầu vào tương đương.
Anders Kaseorg

3

Java 7+, n = 50 trong ~ 30 giây trên TIO

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.Random;
class Main{
  public static void main(String[] a){

    int n=50;

    Random randomGenerator = new Random();
    int i = n+1;
    int squaredN = n*n;
    int[]randomIntegers = new int[i];
    randomIntegers[n] = squaredN;
    while(true){
      for(i=n; i-->1; ){
        randomIntegers[i] = randomGenerator.nextInt(squaredN);
      }
      Set<Integer> result = new HashSet<>();
      Arrays.sort(randomIntegers);
      for(i=n; i-->0; ){
        result.add(randomIntegers[i+1] - randomIntegers[i]);
      }
      if(!result.contains(0) && result.size()==n){
        System.out.println(result);
        return;
      }
    }
  }
}

Hiện tại, phiên bản trả lời của tôi cho phiên bản golf-code của thử thách này , chỉ với một thay đổi nhỏ: java.util.Random#nextInt(limit)được sử dụng thay (int)(Math.random()*limit)cho số nguyên trong phạm vi [0, n), vì nó nhanh gấp đôi .

Hãy thử trực tuyến.

Giải trình:

Cách tiếp cận được sử dụng:

Mã được chia thành hai phần:

  1. Tạo một danh sách nsố lượng số nguyên ngẫu nhiên tính tổng n squared.
  2. Sau đó, nó kiểm tra xem tất cả các giá trị là duy nhất và không có giá trị nào bằng 0 và nếu là falsey, nó sẽ thử lại bước 1, rửa và lặp lại cho đến khi chúng ta có kết quả.

Bước 1 được thực hiện với các bước phụ sau:

1) Tạo một mảng n-1số lượng số nguyên ngẫu nhiên trong phạm vi [0, n squared). Và thêm 0n squaredvào danh sách này. Điều này được thực hiện trong O(n+1)hiệu suất.
2) Sau đó, nó sẽ sắp xếp mảng với nội dung java.util.Arrays.sort(int[]), Điều này được thực hiện trong O(n*log(n))hiệu suất, như được nêu trong các tài liệu:

Sắp xếp mảng int được chỉ định theo thứ tự số tăng dần. Thuật toán sắp xếp là một quicksort được điều chỉnh, được chuyển thể từ "Kỹ thuật sắp xếp chức năng" của Jon L. Bentley và M. Douglas McIlroy, Thực hành phần mềm và kinh nghiệm, Vol. 23 (11) P. 1249-1265 (tháng 11 năm 1993). Thuật toán này cung cấp hiệu suất n * log (n) trên nhiều tập dữ liệu khiến cho các quicksort khác giảm xuống thành hiệu suất bậc hai.

3) Tính hiệu số giữa mỗi cặp. Danh sách kết quả khác biệt này sẽ chứa các nsố nguyên tính tổng n squared. Điều này được thực hiện trong O(n)hiệu suất.

Dưới đây là một ví dụ:

// n = 4, nSquared = 16

// n-1 amount of random integers in the range [0, nSquared):
[11, 2, 5]

// Add 0 and nSquared to it, and sort:
[0, 2, 5, 11, 16]

// Calculate differences:
[2, 3, 6, 5]

// The sum of these differences will always be equal to nSquared
sum([2, 3, 6, 5]) = 16

Vì vậy, ba bước trên là khá tốt cho hiệu suất, không giống như bước 2 và vòng lặp xung quanh toàn bộ, đó là một lực lượng vũ phu cơ bản. Bước 2 được chia theo các bước phụ sau:

1) Danh sách khác biệt đã được lưu trong a java.util.Set. Nó sẽ kiểm tra xem kích thước của Bộ này có bằng không n. Nếu có, nó có nghĩa là tất cả các giá trị ngẫu nhiên chúng tôi tạo ra là duy nhất.
2) Và nó cũng sẽ kiểm tra xem nó không chứa 0trong Set, kể từ khi thách thức yêu cầu cho các giá trị ngẫu nhiên trong phạm vi [1, X], nơi Xn squaredtrừ đi khoản [1, ..., n-1], như đã nói bởi @Skidsdev trong các bình luận bên dưới.

Nếu một trong hai tùy chọn ở trên (không phải tất cả các giá trị là duy nhất hoặc có số 0), nó sẽ tạo ra một mảng mới và Đặt lại bằng cách đặt lại bước 1. Điều này tiếp tục cho đến khi chúng tôi có kết quả. Bởi vì điều này, thời gian có thể thay đổi khá nhiều. Tôi đã thấy nó hoàn thành trong 3 giây một lần trên TIO n=50, nhưng cũng chỉ trong 55 giây một lần cho n=50.

Chứng minh tính đồng nhất:

Tôi không hoàn toàn chắc chắn làm thế nào để chứng minh điều này là hoàn toàn trung thực. Các java.util.Random#nextIntđồng đều chắc chắn, như được mô tả trong các tài liệu:

Trả về giả danh tiếp theo, intgiá trị được phân phối đồng đều từ chuỗi trình tạo số ngẫu nhiên này. Hợp đồng chung nextIntlà một intgiá trị được tạo ra và trả lại giả. Tất cả 2 32int giá trị có thể được tạo ra với (xấp xỉ) xác suất bằng nhau.

Sự khác biệt giữa các giá trị ngẫu nhiên (được sắp xếp) này tất nhiên không đồng nhất, nhưng các bộ tổng thể là đồng nhất. Một lần nữa, tôi không chắc làm thế nào để chứng minh điều này một cách toán học, nhưng đây là một tập lệnh sẽ đặt 10,000các tập hợp được tạo (cho n=10) vào một Bản đồ với một bộ đếm , trong đó hầu hết các tập hợp là duy nhất; một số lặp lại hai lần; và sự xuất hiện lặp lại tối đa thường là trong phạm vi [4,8].

Hướng dẫn cài đặt:

Vì Java là một ngôn ngữ khá nổi tiếng với nhiều thông tin có sẵn về cách tạo và chạy mã Java, tôi sẽ nói ngắn gọn về điều này.
Tất cả các công cụ được sử dụng trong mã của tôi đều có sẵn trong Java 7 (thậm chí có thể đã có trong Java 5 hoặc 6, nhưng hãy sử dụng 7 chỉ trong trường hợp). Tôi khá chắc chắn rằng Java 7 đã được lưu trữ, vì vậy tôi khuyên bạn nên tải xuống Java 8 để chạy mã của mình.

Suy nghĩ về cải tiến:

Tôi muốn tìm một cải tiến cho việc kiểm tra các số không và kiểm tra tất cả các giá trị là duy nhất. Tôi có thể kiểm tra 0trước, bằng cách đảm bảo giá trị ngẫu nhiên mà chúng ta thêm vào mảng chưa có trong đó, nhưng điều đó có nghĩa là một vài điều: mảng phải là một ArrayListphương thức để chúng ta có thể sử dụng phương thức dựng sẵn .contains; một vòng lặp while nên được thêm vào cho đến khi chúng tôi tìm thấy một giá trị ngẫu nhiên chưa có trong Danh sách. Vì việc kiểm tra số 0 hiện được thực hiện với .contains(0)Bộ (chỉ được kiểm tra một lần), nên kiểm tra hiệu suất tại thời điểm đó tốt hơn, so với việc thêm vòng lặp .containsvào Danh sách, sẽ được kiểm tra ít nhất nnhiều lần , nhưng rất có thể nhiều hơn.

Đối với kiểm tra tính duy nhất, chúng tôi chỉ có nsố lượng số nguyên ngẫu nhiên tổng hợp n squaredsau bước 1 của chương trình, vì vậy chỉ sau đó chúng tôi mới có thể kiểm tra xem tất cả có phải là duy nhất hay không. Có thể giữ một Danh sách có thể sắp xếp thay vì mảng và kiểm tra sự khác biệt ở giữa, nhưng tôi thực sự nghi ngờ rằng nó sẽ cải thiện hiệu suất hơn là chỉ đưa chúng vào Setvà kiểm tra xem kích thước của Bộ đó có phải là nmột lần không.


1
nếu nó giúp tốc độ, không có số nào trong tập hợp có thể lớn hơn n^2 - sum(1..n-1)ví dụ cho n=5số hợp lệ lớn nhất là5^2 - sum(1, 2, 3, 4) == 25 - 10 == 15
Skidsdev

@Skidsdev Cảm ơn, đã không nghĩ về điều đó. Mặc dù với cách tiếp cận hiện tại của tôi, tôi không thể sử dụng nó, vì tôi nhận được sự khác biệt giữa các cặp ngẫu nhiên, thay vì trực tiếp các giá trị ngẫu nhiên. Nhưng nó có thể hữu ích cho các câu trả lời khác có lẽ.
Kevin Cruijssen

1
Kích thước của tập kết quả không bao giờ có thể nhiều hơn n, phải không? Trong trường hợp đó, bạn có thể thêm 0vào tập hợp, và sau đó kiểm tra xem kích thước có (hiện tại) nhiều hơn không n. Điều này chỉ có thể xảy ra nếu sự khác biệt là khác không và khác biệt.
Neil

@Neil Oh, điều đó khá thông minh, và tôi chắc chắn sẽ sử dụng nó trong câu trả lời golf-code của tôi để chơi golf một vài byte. Tôi không chắc chắn nếu nó sẽ cải thiện hiệu suất ở đây, mặc dù. HashSet.containstrong hầu hết các trường hợp gần O(1)và trong trường hợp xấu nhất là O(n)trong Java 7 và O(log n)trong Java 8+ (nó đã được cải thiện sau khi họ thay thế chuỗi bằng phát hiện va chạm). Nếu tôi được phép trả lại Bộ có thêm 0cho kiểm tra, thì thực sự hiệu suất sẽ tốt hơn một chút, nhưng nếu tôi phải gọi set.remove(0);bên trong nếu, tôi khá chắc chắn rằng hiệu suất có phần giống nhau.
Kevin Cruijssen

Ồ, tôi quên mất bạn cũng cần phải trả lại bộ ảnh ... đừng bận tâm.
Neil

1

Toán học n = 11

(While[Tr@(a=RandomSample[Range[#^2-#(#-1)/2],#])!=#^2];a)&     
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.