Divulgue avidamente a lista de combinações com repetição

10

Primeiro, algumas definições:

  • Dado ne k, considere a lista classificada de multisets , onde, para cada multiset, escolhemos knúmeros {0, 1, ..., n-1}com repetições.

Por exemplo, para n=5e k=3, temos:

[(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 0, 4), (0, 1, 1), ( 0, 1, 2), (0, 1, 3), (0, 1, 4), (0, 2, 2), (0, 2, 3), (0, 2, 4), (0, 3, 3), (0, 3, 4), (0, 4, 4), (1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4), (1, 2, 2), (1, 2, 3), (1, 2, 4), (1, 3, 3), (1, 3, 4), (1, 4, 4) , (2, 2, 2), (2, 2, 3), (2, 2, 4), (2, 3, 3), (2, 3, 4), (2, 4, 4), ( 3, 3, 3), (3, 3, 4), (3, 4, 4), (4, 4, 4)]

  • Uma parte é uma lista de multisets com a propriedade de pelo menos o tamanho da interseção de todos os multisets da peça k-1. Ou seja, pegamos todos os multisets e os cruzamos (usando a interseção multiset) de uma só vez. Como exemplos, [(1, 2, 2), (1, 2, 3), (1, 2, 4)]é uma parte, pois sua interseção é do tamanho 2, mas [(1, 1, 3),(1, 2, 3),(1, 2, 4)]não é, porque sua interseção é do tamanho 1.

Tarefa

Seu código deve receber dois argumentos ne k. Ele deve avidamente percorrer esses multisets na ordem classificada e exibir as partes da lista. Para o caso n=5, k=3, o particionamento correto é:

(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 0, 4)
(0, 1, 1), (0, 1, 2), (0, 1, 3), (0, 1, 4)
(0, 2, 2), (0, 2, 3), (0, 2, 4)
(0, 3, 3), (0, 3, 4)
(0, 4, 4)
(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)
(1, 2, 2), (1, 2, 3), (1, 2, 4)
(1, 3, 3), (1, 3, 4)
(1, 4, 4)
(2, 2, 2), (2, 2, 3), (2, 2, 4)
(2, 3, 3), (2, 3, 4)
(2, 4, 4)
(3, 3, 3), (3, 3, 4)
(3, 4, 4), (4, 4, 4)

Aqui está outro exemplo para n = 4, k = 4.

(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)
(0, 0, 1, 1), (0, 0, 1, 2), (0, 0, 1, 3)
(0, 0, 2, 2), (0, 0, 2, 3)
(0, 0, 3, 3)
(0, 1, 1, 1), (0, 1, 1, 2), (0, 1, 1, 3)
(0, 1, 2, 2), (0, 1, 2, 3)
(0, 1, 3, 3)
(0, 2, 2, 2), (0, 2, 2, 3)
(0, 2, 3, 3), (0, 3, 3, 3)
(1, 1, 1, 1), (1, 1, 1, 2), (1, 1, 1, 3)
(1, 1, 2, 2), (1, 1, 2, 3)
(1, 1, 3, 3)
(1, 2, 2, 2), (1, 2, 2, 3)
(1, 2, 3, 3), (1, 3, 3, 3)
(2, 2, 2, 2), (2, 2, 2, 3)
(2, 2, 3, 3), (2, 3, 3, 3)
(3, 3, 3, 3)

Esclarecimento sobre o que significa ganancioso: Para cada multiset, por sua vez, procuramos ver se ele pode ser adicionado à peça existente. Se pudermos adicioná-lo. Se não pudermos, começamos uma nova peça. Observamos os multisets na ordem classificada, como no exemplo acima.

Resultado

Você pode gerar o particionamento em qualquer formato que desejar. No entanto, multisets devem ser escritos horizontalmente em uma linha. Esse é um conjunto múltiplo individual não deve ser gravado verticalmente ou espalhado por várias linhas. Você pode escolher como separa a representação de peças na saída.

Premissas

Nós podemos assumir isso n >= k > 0.

Jonathan Allan
fonte
@LuisMendo Acabei de cometer um erro. Eu quis dizer que multisets devem ser escritos horizontalmente em uma linha.
No primeiro caso de teste, por que é (0, 4, 4)por si só? Dada a sua descrição, eu acho que seria a sua "parte" (0, 4, 4), (1, 4, 4), (2, 4, 4), (3, 4, 4), (4, 4, 4). Da mesma forma, (0, 0, 3, 3)no segundo caso de teste.
Greg Martin
@GregMartin Por causa da ganância do método. Você está certo de que, em geral, será subótimo. O número mínimo de peças que você pode receber por um método não ganancioso é um interessante se pergunta difícil,
Ah, você literalmente quer dizer que, uma vez que o próximo termo não corresponda à parte "ativa", essa parte será encerrada para sempre. Está bem.
Greg Martin

Respostas:

4

Geléia , 26 25 bytes

œ&µL‘<⁴ȧ⁹ȯ
œċµç\L€=⁴œṗµḊ’

Programa completo que imprime uma representação de uma lista de listas, cada uma fazendo parte, por exemplo, para n = 5, k = 3:

[[[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 0, 4]], [[0, 1, 1], [0, 1, 2], [0, 1, 3], [0, 1, 4]], [[0, 2, 2], [0, 2, 3], [0, 2, 4]], [[0, 3, 3], [0, 3, 4]], [0, 4, 4], [[1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 1, 4]], [[1, 2, 2], [1, 2, 3], [1, 2, 4]], [[1, 3, 3], [1, 3, 4]], [1, 4, 4], [[2, 2, 2], [2, 2, 3], [2, 2, 4]], [[2, 3, 3], [2, 3, 4]], [2, 4, 4], [[3, 3, 3], [3, 3, 4]], [[3, 4, 4], [4, 4, 4]]]

Nota: a representação usada remove as listas redundantes [ e em ] torno do comprimento 1.

Experimente online! ou veja uma versão impressa bonita (custo 3 bytes)

Como?

œ&µL‘<⁴ȧ⁹ȯ - Link 1, conditional multi-set intersection: list x, list y
œ&         - multi-set intersection(x, y)
  µ        - monadic chain separation (call that i)
   L       - length(i)
    ‘      - increment
     <     - less than?:
      ⁴    -     2nd program input, k
       ȧ   - logical and with:
        ⁹  -     link's right argument, y (y if i is too short, else 0)
         ȯ - logical or (y if i is too short, else i)

œċµç\L€=⁴œṗµḊ’ - Main link: n, k
œċ             - combinations with replacement(n, k) (sorted since n implies [1,n])
  µ            - monadic chain separation (call that w)
         œṗ    - partition w at truthy indexes of:
   ç\          -     reduce w with last link (1) as a dyad
     L€        -     length of €ach
        ⁴      -     2nd program input, k
       =       -     equal (vectorises)
           µ   - monadic chain separation
            Ḋ  - dequeue (since the result will always start with an empty list)
             ’ - decrement (vectorises) (since the Natural numbers were used by œċ)
Jonathan Allan
fonte
Isso é ótimo. Obrigado.
3

MATLAB, 272 bytes

function g(n,k);l=unique(sort(nchoosek(repmat(0:n-1,1,k),k),2),'rows');p=zeros(0,k);for i=1:size(l,1)p=[p;l(i,:)];a=0;for j=1:size(p,1)for m=1:size(p,1)b=0;for h=1:k if(p(j,h)==p(m,h))b=b+1;end;end;if(b<k-1)a=1;end;end;end;if(a)fprintf('\n');p=l(i,:);end;disp(l(i,:));end;

Resultado:

>> g(5,3)
 0     0     0

 0     0     1

 0     0     2

 0     0     3

 0     0     4


 0     1     1

 0     1     2

 0     1     3

 0     1     4


 0     2     2

 0     2     3

 0     2     4


 0     3     3

 0     3     4


 0     4     4


 1     1     1

 1     1     2

 1     1     3

 1     1     4


 1     2     2

 1     2     3

 1     2     4


 1     3     3

 1     3     4


 1     4     4


 2     2     2

 2     2     3

 2     2     4


 2     3     3

 2     3     4


 2     4     4


 3     3     3

 3     3     4


 3     4     4

 4     4     4

>> g(4,4)
 0     0     0     0

 0     0     0     1

 0     0     0     2

 0     0     0     3


 0     0     1     1

 0     0     1     2

 0     0     1     3


 0     0     2     2

 0     0     2     3


 0     0     3     3


 0     1     1     1

 0     1     1     2

 0     1     1     3


 0     1     2     2

 0     1     2     3


 0     1     3     3


 0     2     2     2

 0     2     2     3


 0     2     3     3

 0     3     3     3


 1     1     1     1

 1     1     1     2

 1     1     1     3


 1     1     2     2

 1     1     2     3


 1     1     3     3


 1     2     2     2

 1     2     2     3


 1     2     3     3

 1     3     3     3


 2     2     2     2

 2     2     2     3


 2     2     3     3

 2     3     3     3


 3     3     3     3

Duas linhas vazias entre partes diferentes.

Ungolfed:

function g(n,k);
l=unique(sort(nchoosek(repmat(0:n-1,1,k),k),2),'rows');
p=zeros(0,k);
for i=1:size(l,1)
    p=[p;l(i,:)];
    a=0;
    for j=1:size(p,1)
        for m=1:size(p,1)
            b=0;
            for h=1:k
                if(p(j,h)==p(m,h))
                    b=b+1;
                end;
            end;
                if(b<k-1)
                    a=1;
                end;
        end;
    end;
    if(a)
        fprintf('\n');
        p=l(i,:);
    end;
    disp(l(i,:));
end;

Explicação:

Primeiro, encontramos todos os multisets com força bruta:

l=unique(sort(nchoosek(repmat(0:n-1,1,k),k),2),'rows');

repmat(0:n-1, 1, k)repete o vetor de valores de 0para n-1 kvezes.

nchoosek(x, k) retorna uma matriz contendo todas as combinações k do vetor repetido.

sort(x, 2)classifica todas as combinações k e unique(x, 'rows')remove todas as duplicatas.

p=zeros(0,k);cria uma matriz vazia com kcolunas. Vamos usá-lo como uma pilha. Em cada iteração do outernmost forloop, que primeiro adicionar o multiset atual para a referida pilha: p=[p;l(i,:)];.

Em seguida, verificamos se a interseção de todos os multisets na pilha é pelo menos k-1longa com o código a seguir (não podemos usar o intersectcomando do MATLAB para verificar as interseções, pois ele retorna um conjunto, mas precisamos de um multiset):

a=0;
for j=1:size(p,1)
    for m=1:size(p,1)
        b=0;
        for h=1:k 
            if(p(j,h)==p(m,h))
                b=b+1;
            end;
        end;
        if(b<k-1)
            a=1;
        end;
    end;
end;

Agora, se a interseção for longa o suficiente a == 0, caso contrário a == 1.

Se a interseção não for longa o suficiente, imprimimos uma nova linha e esvaziamos a pilha:

if(a)
    fprintf('\n');
    p=l(i,:); % Only the current multiset will be left in the stack.
end;

Em seguida, apenas imprimimos o multiset atual:

disp(l(i,:));
Steadybox
fonte
Parece que você quebrou! Você poderia explicar seu método?
@Lembik eu adicionei uma explicação.
Steadybox
3

MATL , 34 bytes

vi:qiZ^!S!Xu!"@!&vt1&dXasq?0&Y)0cb

As peças são separadas por uma linha que contém espaços em branco.

Experimente online!

Explicação

Isenção de responsabilidade: este método parece funcionar (e funciona nos casos de teste), mas não tenho uma prova de que sempre funcione

As multisets são classificadas internamente (ou seja, cada multiset possui entradas não decrescentes) e externamente (ou seja, o multiset M vem antes do multiset N se M precede N lexicograficamente).

Para calcular a interseção multiset, os multisets classificados são organizados como linhas de uma matriz e as diferenças consecutivas são calculadas ao longo de cada coluna. Se todas as colunas, exceto no máximo uma, tiverem todas as diferenças iguais a zero, os vários conjuntos pertencerão à mesma parte.

Este teste daria um resultado falso negativo para multisets como (1,2,3)e (2,3,4): mesmo se 2, 3são entradas comuns, eles não seriam detectados como tal, porque eles estão em colunas não correspondentes.

No entanto, isso não parece ser um problema, pelo menos nos casos de teste. Parece que um teste entre multisets como 1,2,3e 2,3,4nunca precisa ser feito, porque alguns multisets intermediários deram um resultado negativo e, portanto, eles já estão em partes diferentes. Se isso é realmente verdade, o motivo, sem dúvida, tem a ver com o fato de os multisets serem classificados.

Eu não tenho uma prova disso, no entanto. Parece apenas funcionar.

v           % Concatenate stack vertically: gives an empty array. This will
            % grow into the first part
i:q         % Input n. Push [0 1 ... n-1]
i           % Input k
Z^          % Cartesian power. Each Cartesian tuple is on a row
!S!         % Sort each row
Xu          % Unique rows. This gives all multisets, sorted, each on a row
!           % Transpose
"           % For each column
  @!        %   Push current multiset as a row
  &v        %   Vertically concatenate with the part so far
  t         %   Duplicate
  1&d       %   Consecutive differences along each column
  Xas       %   Number of columns that contain at least one non-zero entry
  q?        %   If that number is not 1 (this means that the current 
            %   multiset should begin a new part)
    0&Y)    %     Push last row, then the array with the remaining rows.
            %     Said array is a part, which we now know is complete
    0c      %     Push character 0. This will be shown as a line containing 
            %     a space. This is used as a separator between parts.
    b       %     Bubble up. This moves the loose row to the top. This row 
            %     is the beginning of a new part
            %   Implicitly end if
            % Implicitly end for
            % Implicitly display
Luis Mendo
fonte
É muito impressionante.
Estou tentando entender se o método que você descreve sempre funcionará. Vejo que, no n=k=4caso de começarmos uma nova peça (0, 0, 3, 3), a diferença consecutiva vetorizada disso e do conjunto múltiplo anterior (0, 0, 2, 3)tem apenas uma diferença; então, como a "peça até agora" faz isso funcionar? (ou equivalentemente qual foi o resultado etapa anterior que foi usado em vez de (0, 0, 2, 3)?)
Jonathan Allan
Ah, vejo que você faz uma diferença consecutiva. Sim, isso sempre deve funcionar! Você está literalmente procurando os pontos nos quais mais de um item é alterado, mas, em vez de uma interseção de vários conjuntos, a interseção simplesmente vetorizada - que funcionará desde que os conjuntos de mutli sejam ordenados para começar.
Jonathan Allan
@ JonathanAllan Sim, é diferença consecutiva ao invés de interseção. Eu ainda não vejo isso claro que vai sempre trabalho, mas se você diz ... :-)
Luis Mendo
1

PHP, 245 bytes

for(;$i<($n=$argv[1])**$m=$argv[2];$i++){for($a=[],$v=$i;$v|count($a)<$m;$v=$v/$n^0)array_unshift($a,$v%$n);sort($a);in_array($a,$r)?:$r[]=$a;}foreach($r as$k=>$v)$k&&count(array_diff_assoc($x[$c][0],$v))<2?$x[$c][]=$v:$x[++$c][]=$v;print_r($x);

Experimente online!

Expandido

for(;$i<($n=$argv[1])**$m=$argv[2];$i++){ # loop till $argv[1]**$argv[2]
    for($a=[],$v=$i;$v|count($a)<$m;$v=$v/$n^0) 
    array_unshift($a,$v%$n); # create base n array
    sort($a); #sort array
    in_array($a,$r)?:$r[]=$a; # if sorted array is not in result add it
}    
foreach($r as$k=>$v)
    $k&& # > first item and
    count(array_diff_assoc($x[$c][0],$v))<2 # if difference is only 1 item between actual item and first item in last storage item
    ?$x[$c][]=$v # add item in last storage array
    :$x[++$c][]=$v; # make a new last storage array
print_r($x); # Output as array

Saída como String

foreach($x as$y){$p=[];
foreach($y as$z){$p[]=$o="(".join(",",$z).")";}
    echo join(", ",$p)."\n";
}

n> 15 para mais precisão

for($i=0;$i<bcpow($argv[1],$argv[2]);$i=bcadd($i,1)){
    for($a=[],$v=$i;$v|count($a)<$argv[2];$v=bcdiv($v,$argv[1]))
    array_unshift($a,bcmod($v,$argv[1]));
    sort($a);
    in_array($a,$r)?:$r[]=$a;
}
Jörg Hülsermann
fonte
Isso parece funcionar! Mas o que você quer dizer com mais precisão?
@Lembik a versão curta dá de volta 0para (16**16-1)%16e do longo trabalho versão somente com a precisão que é necessário para n>15 bcmod(bcsub(bcpow(16,16),1),16)se 15 php.net/manual/en/ref.bc.php
Jörg Hülsermann