임의의 임의성 (스피드 에디션)


10

integer가 주어지면 n, 세트의 합이 같도록 n범위 내에서 임의의 고유 정수 세트를 계산하십시오 1..n^2(포함).n^2

이 경우 랜덤은 유효한 출력간에 균일하게 랜덤을 의미 합니다. 주어진 각각의 유효한 출력 n은 일정하게 생성 될 가능성이 있어야합니다.

예를 들어, n=31/3 기회를 출력의 각해야한다 6, 1, 2, 3, 5, 1또는를 4, 3, 2. 이 집합이므로, 순서는 상관이 4, 3, 2동일3, 2, 4

채점

승자는 n60 초 안에 최고치 를 계산할 수있는 프로그램입니다 .
참고 : 부분 하드 코딩을 방지하려면 모든 항목이 4000 바이트 미만이어야합니다.

테스팅

모든 코드는 로컬 Windows 10 시스템 (GPU를 남용하려는 경우 Razer Blade 15, 16GB RAM, Intel i7-8750H 6 코어, 4.1GHz, GTX 1060)에서 실행되므로 자세한 코드 실행 지침을 제공하십시오. 내 기계.
요청에 따라 WSL의 Debian 또는 Xubuntu 가상 머신 (위와 동일한 머신에서)을 통해 항목을 실행할 수 있습니다.

제출물은 연속 50 회 실행되며 최종 점수는 평균 50 개 결과입니다.



4000 바이트 미만인 경우 하드 코딩 비트가 허용됩니까?
Quintec

@Quintec 아니요, 하드 코딩은 표준 허점이므로 기본적으로 금지되어 있습니다. 까다로운 점은 하드 코딩도 관찰 할 수없는 기준으로 간주되므로 허점에서 허용하지 않는 범위에서 공식적으로 "하드 코딩 없음"이라고 말할 수 없습니다. 따라서 바이트 제한입니다. 즉 : 제발 하지 하드 코드하지
Skidsdev

1
대부분의 제출은 거부 방법을 사용하므로 실행 시간은 임의적이며 변동성이 큽니다. 그 어려운시기 만든다
루이스 Mendo

2
오, 잊어 버렸습니다-일부 솔루션은 저품질의 RNG를 빠르게 사용하기로 결정할 수 있기 때문에 n을 취하고 (1..n)의 임의의 숫자를 생성하는 블랙 박스 루틴을 제공해야 할 수 있습니다. 그것을 사용하는 해결책.
user202729

답변:


6

, n ≈ 1400

달리는 방법

로 빌드 cargo build --release하고로 실행하십시오 target/release/arbitrary-randomness n.

이 프로그램은 많은 양의 메모리로 가장 빠르게 실행됩니다 (물론 스왑하지 않는 한). MAX_BYTES현재 8GiB로 설정된 상수 를 편집하여 메모리 사용량을 조정할 수 있습니다 .

작동 원리

이 세트는 일련의 이진 결정 (각 번호가 세트 내부 또는 외부에 있음)으로 구성되며, 각각의 확률은 동적 프로그래밍을 사용하여 각 선택 후에 구성 가능한 가능한 세트 수를 계산하여 조합하여 계산됩니다.

이 이항 파티셔닝 전략 의 버전을 사용하면 큰 n의 메모리 사용량 이 줄어 듭니다 .

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");
    }
}

온라인으로 사용해보십시오!

(참고 : TIO 버전에는 몇 가지 수정 사항이 있습니다. 첫째, 메모리 제한이 1GiB로 줄어 듭니다. 둘째, TIO에서 Cargo.toml와 같은 외부 상자 를 작성 하고 의존 하지 않기 때문에 rand대신 drand48C 라이브러리에서 FFI. 나는 그것을 뿌릴 염려하지 않았으므로 TIO 버전은 모든 실행에서 동일한 결과를 얻을 것입니다. 공식 벤치마킹에 TIO 버전을 사용하지 마십시오.)


부동 소수점 형식은 유한하기 때문에 ln_add_exp절대 차이가 ~ 15 이상인지 확인 하여 최적화 할 수 있습니다. 이러한 차이가 많으면 더 빠를 수 있습니다.
user202729

@ user202729 아니요, 거의 모든 ln_add_exp통화에는 비슷한 입력이 필요합니다.
Anders Kaseorg

3

Java 7 이상, TIO에서 30 초 안에 n = 50

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

이 도전 과제의 코드 골프 버전에 대한 내 대답 의 ungolfed 버전은 하나의 사소한 변경 만 있습니다. 범위의 정수 java.util.Random#nextInt(limit)대신 약 2 배 빠릅니다 .(int)(Math.random()*limit)[0, n)

온라인으로 사용해보십시오.

설명:

사용 된 접근법 :

코드는 두 부분으로 나뉩니다.

  1. n합산되는 임의의 정수 목록을 생성합니다 n squared.
  2. 그런 다음 모든 값이 고유하고 0이 아닌지 확인하고 둘 중 하나라도 거짓이면 1 단계를 다시 시도하여 결과가 나올 때까지 헹구고 반복합니다.

1 단계는 다음 하위 단계로 수행됩니다.

1) n-1범위 내에서 임의의 정수로 구성된 배열을 생성하십시오 [0, n squared). 그리고 추가 0하고 n squared이 목록. 이것은 O(n+1)성능 에서 이루어집니다 .
2) 그런 다음 내장으로 배열을 정렬합니다 java.util.Arrays.sort(int[]). 이것은 O(n*log(n))문서에 명시된 것처럼 성능 이 수행됩니다 .

지정된 정수 배열을 오름차순으로 정렬합니다. 정렬 알고리즘은 Jon L. Bentley와 M. Douglas McIlroy의 "정렬 기능 엔지니어링", Software-Practice and Experience, Vol. 23 (11) P. 1249-1265 (1993 년 11 월). 이 알고리즘은 다른 빠른 정렬이 2 차 성능으로 저하되도록하는 많은 데이터 세트에서 n * log (n) 성능을 제공합니다.

3) 각 쌍의 차이를 계산하십시오. 이 결과 차이 목록에는 n에 합산 된 정수 가 포함 됩니다 n squared. 이것은 O(n)성능 에서 이루어집니다 .

예를 들면 다음과 같습니다.

// 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

따라서 위의 세 단계는 기본 무차별 대입 인 2 단계 및 전체 주변의 루프와 달리 성능에 매우 좋습니다. 2 단계는 다음 하위 단계로 나뉩니다.

1) 차이점 목록이 이미에 저장되어 있습니다 java.util.Set. 이 세트의 크기가 같은지 확인합니다 n. 그렇다면, 우리가 생성 한 모든 임의의 값이 고유하다는 것을 의미합니다.
2) 그리고 그것은 또한이 더 포함되어 있는지 확인하지 않습니다 0도전의 범위에서 임의의 값을 요구하기 때문에, 설정의 [1, X]경우, Xn squared마이너스의 합 [1, ..., n-1]에 의해 명시된 바와 같이, @Skidsdev 아래의 코멘트에.

위의 두 옵션 중 하나 (모든 값이 고유하지 않거나 0이 존재하지 않음) 인 경우 새 배열을 생성하고 1 단계로 재설정하여 다시 설정합니다. 결과가 나올 때까지 계속됩니다. 이로 인해 시간이 약간 다를 수 있습니다. 나는 TIO에서 3 초에 한 번 완료 n=50되지만 55 초에 한 번 완료되는 것을 보았습니다 n=50.

균일 성 입증 :

나는 이것을 완전히 정직하게 증명하는 방법을 완전히 확신하지 못한다. 는 java.util.Random#nextInt해당 문서에 설명 된대로, 확실히 균일 :

int이 난수 생성기 시퀀스에서 균일하게 분포 된 다음 의사 난수 값을 반환합니다 . 일반적인 계약은 nextInt하나의 int값이 의사 난수로 생성되어 반환 된다는 것 입니다. 가능한 모든 32 개의int 값은 (대략) 동일한 확률로 생성됩니다.

이러한 (정렬 된) 임의의 값 자체의 차이는 물론 균일하지 않지만 전체적으로 세트는 균일합니다. 다시 말하지만, 이것을 수학적으로 증명하는 방법을 잘 모르겠지만 여기에 10,000생성 된 세트를 for n=10와 함께 맵에 넣는 스크립트가 있습니다 ( 대부분의 세트는 고유합니다). 일부는 두 번 반복되었습니다. 최대 반복 발생 범위는 일반적으로 범위 [4,8]입니다.

설치 지침 :

Java는 Java 코드를 작성하고 실행하는 방법에 대한 많은 정보를 제공하는 잘 알려진 언어이므로 간단히 설명하겠습니다.
내 코드에 사용 된 모든 도구는 Java 7에서 사용할 수 있습니다 (아마도 Java 5 또는 6에서도 가능하지만 경우에 따라 7을 사용하십시오). Java 7이 이미 보관되어 있다고 확신하므로 Java 8을 다운로드하여 코드를 실행하는 것이 좋습니다.

개선에 관한 생각 :

0 확인을 개선하고 모든 값이 고유한지 확인하고 싶습니다. 0배열에 추가하는 임의의 값이 아직 포함되어 있지 않은지 확인하여 이전에 확인할 수 있었지만 몇 가지 의미가 있습니다. 배열은 ArrayList내장 메소드를 사용할 수 있도록 해야합니다 .contains. 목록에없는 임의의 값을 찾을 때까지 while 루프를 추가해야합니다. 제로를 확인하는 것은 지금으로 수행되기 때문에 .contains(0)(한 번만 체크) 설정에, 그것은 성능과 루프를 추가 비교하여, 그 시점에서 그것을 확인하는 가장 가능성이 더 나은 .contains적어도 확인됩니다 목록에 n배 하지만 대부분 더 많을 것입니다.

고유성 검사 의 경우 프로그램의 1 단계 이후에 n합산되는 임의의 정수만 n squared있으므로 모든 것이 고유한지 여부를 확인할 수 있습니다. 배열 대신 정렬 가능한 목록을 유지하고 그 사이의 차이점을 확인할 수는 있지만, Set세트에 넣는 것보다 성능을 향상시키고 세트의 크기가 n한 번 인지 확인 하는 것이 심각하다고 의심합니다 .


1
이 속도를 도움이된다면, 세트에는 수보다 클 수 없습니다 n^2 - sum(1..n-1)에 대한 예를 들어 n=5가장 큰 유효 숫자가5^2 - sum(1, 2, 3, 4) == 25 - 10 == 15
Skidsdev

@Skidsdev 감사합니다, 그것에 대해 생각하지 않았습니다. 현재 접근 방식으로는 임의의 값 대신 직접 임의 쌍 사이의 차이를 얻으므로 사용할 수 없습니다. 그러나 아마도 다른 답변에 유용 할 수 있습니다.
Kevin Cruijssen

1
결과 집합의 크기는을 초과 n할 수 없습니다. 이 경우 0세트에 추가 한 다음 크기가 (현재) 이상인지 확인할 수 있습니다 n. 차이가 모두 0이 아닌 다른 경우에만 발생할 수 있습니다.
Neil

@ Neil 아, 그것은 똑똑합니다. 그리고 나는 골프 골프에 몇 바이트를 줄이려고 확실히 사용할 것입니다. 그래도 성능이 향상 될지 확실하지 않습니다. HashSet.contains대부분의 경우에 가깝고 O(1)최악의 경우 O(n)Java 7 및 O(log n)Java 8 이상입니다 (체인을 충돌 감지로 교체 한 후에 개선되었습니다). 0확인을 위해 추가 된 세트를 반환 할 수 있다면 실제로 성능이 약간 더 좋지만 if set.remove(0);내부 를 호출 해야하는 경우 성능이 다소 동일하다고 확신합니다.
케빈 크루이 ssen

오, 세트도 반납해야한다는 걸 잊었 어.

1

매스 매 티카 n = 11

(While[Tr@(a=RandomSample[Range[#^2-#(#-1)/2],#])!=#^2];a)&     
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.