Dado inteiro n
, calcule um conjunto de n
números inteiros únicos aleatórios no intervalo 1..n^2
(inclusive), de modo que a soma do conjunto seja igual an^2
Aleatório, nesse caso, significa uniformemente aleatório entre saídas válidas. Cada saída válida para um dado n
deve ter uma chance uniforme de ser gerada.
Por exemplo, n=3
devem ter uma oportunidade 1/3 cada de saída 6, 1, 2
, 3, 5, 1
, ou 4, 3, 2
. Como este é um conjunto, a ordem é irrelevante, 4, 3, 2
é idêntica à3, 2, 4
Pontuação
O vencedor é o programa que pode calcular o mais alto n
em menos de 60 segundos.
Nota: Para evitar uma possível codificação parcial parcial, todas as entradas devem ter menos de 4000 bytes
Teste
Todo o código será executado na minha máquina Windows 10 local (Razer Blade 15, 16 GB de RAM, Intel i7-8750H 6 núcleos, 4.1 GHz, GTX 1060 no caso de você querer abusar da GPU); por isso, forneça instruções detalhadas para executar seu código em minha maquina
A pedido, as entradas podem ser executadas via Debian na WSL ou em uma Máquina Virtual Xubuntu (ambas na mesma máquina que acima)
As inscrições serão realizadas 50 vezes consecutivas, a pontuação final será uma média de todos os 50 resultados.
fonte
Respostas:
Ferrugem , n ≈ 1400
Como executar
Construa com
cargo build --release
e execute comtarget/release/arbitrary-randomness n
.Este programa roda mais rápido com muita memória (desde que não esteja trocando, é claro). Você pode ajustar o uso de memória editando a
MAX_BYTES
constante, atualmente definida em 8 GiB.Como funciona
O conjunto é construído por uma sequência de decisões binárias (cada número é dentro ou fora do conjunto), cujas probabilidades são calculadas combinatorialmente, contando o número de possíveis conjuntos construtíveis após cada escolha usando programação dinâmica.
O uso de memória para n grande é reduzido usando uma versão dessa estratégia de particionamento binomial .
Cargo.toml
src/main.rs
Experimente online!
(Nota: a versão do TIO tem algumas modificações. Primeiro, o limite de memória é reduzido para 1 GiB. Segundo, como o TIO não permite que você escreva ae
Cargo.toml
dependa de caixas externas comorand
, em vez disso, extraídrand48
da biblioteca C usando o FFI. Como não me preocupei em propagar isso, a versão TIO produzirá o mesmo resultado a cada execução. Não use a versão TIO para comparações oficiais.)fonte
ln_add_exp
verificando se a diferença absoluta é maior que ~ 15 ou mais, o que pode ser mais rápido se houver muita adição.ln_add_exp
chamadas envolvem entradas comparáveis.Java 7+, n = 50 in ~ 30 seg no TIO
A versão ungolfed da minha resposta para a versão code-golf deste desafio, por enquanto, com apenas uma pequena alteração:
java.util.Random#nextInt(limit)
é usada em vez de(int)(Math.random()*limit)
para um número inteiro no intervalo[0, n)
, pois é duas vezes mais rápido .Experimente online.
Explicação:
Abordagem utilizada:
O código é dividido em duas partes:
n
quantidade de números inteiros aleatórios que somamn squared
.A etapa 1 é realizada com as seguintes subetapas:
1) Gere uma matriz de
n-1
quantidade de números inteiros aleatórios no intervalo[0, n squared)
. E adicione0
en squared
a esta lista. Isso é feito noO(n+1)
desempenho.2) Em seguida, ele classificará o array com o builtin
java.util.Arrays.sort(int[])
. Isso é feito noO(n*log(n))
desempenho, conforme declarado nos documentos:3) Calcule a diferença entre cada par. Essa lista resultante de diferenças conterá
n
números inteiros que somamn squared
. Isso é feito noO(n)
desempenho.Aqui está um exemplo:
Portanto, essas três etapas acima são muito boas para o desempenho, diferentemente da etapa 2 e do ciclo da coisa toda, que é uma força bruta básica. A etapa 2 é dividida nestas sub-etapas:
1) A lista de diferenças já está salva em a
java.util.Set
. Ele verificará se o tamanho deste conjunto é igual an
. Se for, significa que todos os valores aleatórios que geramos são únicos.2) E também verificará se não contém nenhum
0
no conjunto, pois o desafio solicita valores aleatórios no intervalo[1, X]
, ondeX
én squared
menos a soma de[1, ..., n-1]
, conforme declarado por @Skidsdev no comentário abaixo.Se uma das duas opções acima (nem todos os valores forem únicos ou houver zero), ela gerará uma nova matriz e definirá novamente redefinindo a etapa 1. Isso continua até que tenhamos um resultado. Por esse motivo, o tempo pode variar bastante. Eu já vi isso terminar em 3 segundos uma vez no TIO
n=50
, mas também em 55 segundos uma vezn=50
.Prova de uniformidade:
Não tenho muita certeza de como provar isso para ser completamente honesto. O
java.util.Random#nextInt
é uniforme, com certeza, conforme descrito nos documentos:As diferenças entre esses valores aleatórios (classificados) não são, obviamente, uniformes, mas os conjuntos como um todo são uniformes. Novamente, não tenho certeza de como provar isso matematicamente, mas aqui está um script que colocará
10,000
conjuntos gerados (paran=10
) em um mapa com um contador , onde a maioria dos conjuntos é única; alguns repetiram duas vezes; e a ocorrência máxima repetida geralmente está no intervalo[4,8]
.Instruções de instalação:
Como o Java é uma linguagem bastante conhecida, com muitas informações disponíveis sobre como criar e executar o código Java, vou manter isso breve.
Todas as ferramentas usadas no meu código estão disponíveis no Java 7 (talvez até no Java 5 ou 6, mas vamos usar 7 apenas por precaução). Eu tenho certeza que o Java 7 já está arquivado, então eu sugiro fazer o download do Java 8 para executar meu código.
Reflexões sobre melhorias:
Gostaria de encontrar uma melhoria para a verificação de zeros e verificar se todos os valores são únicos. Eu poderia verificar
0
antes, certificando-me de que o valor aleatório que adicionamos à matriz ainda não esteja nela, mas isso significaria algumas coisas: a matriz deve ser umaArrayList
para que possamos usar o método interno.contains
; um loop while deve ser adicionado até encontrarmos um valor aleatório que ainda não esteja na lista. Como a verificação de zero agora é feita.contains(0)
no conjunto (que é verificado apenas uma vez), é mais provável que seja melhor verificar o desempenho nesse ponto, em comparação com adicionar o loop.contains
na lista, que será verificado pelo menosn
vezes , mas provavelmente mais.Quanto à verificação de exclusividade, só temos nossa
n
quantidade de números inteiros aleatórios que somamn squared
após a etapa 1 do programa; portanto, só assim podemos verificar se todos são únicos ou não. Pode ser possível manter uma lista classificável em vez de matriz e verificar as diferenças entre elas, mas duvido seriamente que melhore o desempenho do que apenas colocá-las emSet
e verifique se o tamanho desse conjunto én
uma vez.fonte
n^2 - sum(1..n-1)
por exemplo,n=5
o maior número válido é #:5^2 - sum(1, 2, 3, 4) == 25 - 10 == 15
n
, pode? Nesse caso, você pode adicionar0
ao conjunto e verificar se o tamanho é (agora) maior quen
. Isso só pode acontecer se as diferenças forem diferentes de zero e distintas.HashSet.contains
está na maioria dos casos próximo eO(1)
, na pior das hipóteses, estáO(n)
no Java 7 eO(log n)
no Java 8+ (foi aprimorado após a substituição do encadeamento pela detecção de colisão). Se eu puder devolver o conjunto com o valor adicionado0
para a verificação, é realmente um pouco melhor para o desempenho, mas se eu tiver que chamarset.remove(0);
dentro do if, tenho certeza de que o desempenho é um pouco o mesmo.Mathematica n = 11
fonte