Crie um agendador de testes de vinho envenenado

16

Recentemente, no Puzzling.SE, escrevi um problema sobre determinar quais duas garrafas de um número maior são envenenadas quando o veneno é ativado apenas se os dois componentes estiverem bêbados. Acabou sendo uma provação, com a maioria das pessoas conseguindo reduzir para 18 ou 19 prisioneiros usando algoritmos completamente diferentes.

A declaração do problema original é a seguinte:

Você é o governante de um reino medieval que adora dar festas. O cortesão que tentou envenenar uma de suas garrafas de vinho da última vez ficou furioso ao saber que você conseguiu identificar qual garrafa ele havia envenenado em mil com apenas dez prisioneiros.

Desta vez, ele é um pouco mais habilidoso. Ele desenvolveu um veneno composto P : um líquido binário que só é mortal quando dois componentes individualmente inofensivos se misturam; isso é semelhante a como o epóxi funciona. Ele enviou outro engradado de 1.000 garrafas de vinho. Um frasco tem componente C_ae outro tem componente C_b. ( P = C_a + C_b)

Quem bebe os dois componentes morre na meia-noite da noite em que bebeu o componente final, independentemente de quando durante o dia ingeriu o líquido. Cada componente venenoso permanece no corpo até que o segundo componente seja ativado; portanto, se você beber um componente um dia e outro componente no dia seguinte, morrerá à meia-noite no final do segundo dia.

Você tem dois dias antes da sua próxima festa. Qual é o número mínimo de prisioneiros que você precisa usar para testar, a fim de identificar quais duas garrafas estão contaminadas e qual algoritmo você precisa seguir com esse número de prisioneiros?


Bônus
Além disso, suponha que você tivesse um limite fixo de 20 prisioneiros à sua disposição, qual é o número máximo de garrafas que você poderia testar teoricamente e chegar a uma conclusão precisa sobre quais garrafas foram afetadas?

Sua tarefa é criar um programa para resolver o problema do bônus. Dados os nprisioneiros, seu programa criará uma programação de testes que será capaz de detectar as duas garrafas envenenadas entre as mgarrafas, sempre que mfor o maior possível.

Seu programa inicialmente terá como entrada o número N, o número de prisioneiros. Em seguida, ele produzirá:

  • M, o número de garrafas que você tentará testar. Essas garrafas serão rotuladas de 1para M.

  • N linhas, contendo os rótulos das garrafas que cada prisioneiro irá beber.

Seu programa tomará como entrada os prisioneiros que morreram no primeiro dia, com o prisioneiro na primeira linha 1, na próxima linha 2, etc. Em seguida, ele produzirá:

  • Nmais linhas, contendo os rótulos das garrafas que cada prisioneiro irá beber. Prisioneiros mortos terão linhas em branco.

Seu programa tomará como entrada os prisioneiros que morreram no segundo dia e produzirá dois números, AeB , representando quais as duas garrafas que seu programa acha que contêm o veneno.

Um exemplo de entrada para dois prisioneiros e quatro garrafas pode ir como tal, se garrafas 1e 3forem envenenadas:

> 2      // INPUT: 2 prisoners
4        // OUTPUT: 4 bottles
1 2 3    // OUTPUT: prisoner 1 will drink 1, 2, 3
1 4      // OUTPUT: prisoner 2 will drink 1, 4
> 1      // INPUT: only the first prisoner died
         // OUTPUT: prisoner 1 is dead, he can't drink any more bottles
3        // OUTPUT: prisoner 2 drinks bottle 3
> 2      // INPUT: prisoner 2 died
1 3      // OUTPUT: therefore, the poisoned bottles are 1 and 3.

The above algorithm may not actually work in all
cases; it's just an example of input and output.

A programação de testes do seu programa deve determinar com êxito cada possível par de garrafas envenenadas para que seja um envio válido.

Seu programa será pontuado com os seguintes critérios, em ordem:

  • O número máximo de garrafas que pode discernir para o caso N = 20.

  • O número de garrafas para o caso N = 21, e casos sucessivamente mais altos depois disso.

  • O comprimento do código. (O código mais curto vence.)

Joe Z.
fonte
Como será a contribuição se mais de um prisioneiro morrer em um único dia? Nenhum de seus exemplos cobre esse caso, e a especificação é ambígua para mim.
ESultanik
É uma fila única com uma lista separada por espaços de prisioneiros que morreram?
ESultanik
O código mais curto importa mais que o número de garrafas? É produtivo aumentar o comprimento do código para que ele lide com mais uma garrafa, como fiz na minha edição recente?
pppery
O número de garrafas tem prioridade. Se você tornar seu código mais longo e mais complexo para espremer mais garrafas, isso será produtivo.
Joe Z.
No problema original, existem apenas 2 dias para resolver o problema. Essa também é a regra para o desafio? (que limita severamente soluções possíveis, no entanto uma quantidade ilimitada de dias seria fácil)
LukStorms

Respostas:

7

Python 2.7.9 - 21 garrafas

Supondo que a especulação de ESultanik esteja certa sobre qual é a contribuição quando vários prisioneiros morrem

r=raw_input;s=str;j=s.join;p=int(r());z=range;q=z(p);x=z(p+1)
print s(p+1)+"\n"+j("\n",(j(" ",(s(a) for a in x if a!=b)) for b in q))
v=r().split();d=[s(a) for a in q if s(a) not in v];d+=[p]if len(d)==1 else [];
print "\n"*p,;r();print j(" ",[s(a) for a in d])

Algoritmo: todo prisioneiro bebe de todas as garrafas, exceto seu número (o primeiro prisioneiro não bebe a primeira garrafa). Se eles não morrerem, seu número de garrafa é envenenado. Se apenas um prisioneiro sobreviver, a garrafa extra será envenenada.

pppery
fonte
3

Perl 5 , 66 garrafas

(72 garrafas para 21 prisioneiros)

Os prisioneiros são divididos idealmente em 2 grupos. As garrafas são agrupadas em conjuntos.

Cada prisioneiro do grupo 1 beberá de todos os sets, exceto um. Haverá 1 ou 2 sobreviventes. Os 1 ou 2 sets que não foram tomados por eles continuarão no dia 2.

No dia 2, os prisioneiros restantes (incluindo os sobreviventes) bebem de todas as garrafas restantes, exceto uma.
Quando dois prisioneiros sobrevivem, as garrafas que não beberam são envenenadas.
Se apenas um prisioneiro permanecer, a garrafa que todos beberam também é suspeita.

O código inclui funcionalidade extra para facilitar o teste. Quando os frascos envenenados são adicionados como parâmetros adicionais, ele não solicita informações sobre quem morreu.

($p,$f,$l)=@ARGV;
$p=9if!$p;
$m=$p-(2*int($p/4))+1;
$n=$p-$m+2;
$b=$m*(($n+1)/2);
@M=(1..$m);
print"Prisoners: $p\nBottles: $b\n";
# building the sets of items
for$x(@M){
    $j=$k+1;$k+=($n+1)/2;
    $s=join",",($j..$k);
    $A[$x]=$s
}
# assigning the sets to the actors
for$x(@M){
    @T=();
    for$j(@M){if($x!=$j){push@T,split/,/,$A[$j]}}
    print"Prisoner $x drinks @T\n";
    $B[$x]=join",",@T
}
if(!$f||!$l){
    # manual input
    print"Who dies: ";
    $_=<STDIN>;chomp;
    @D=split/ /;
    %h=map{($_,1)}@D;
    @S=grep{!$h{$_}}(@M)
} 
else{
    # calculate who dies based on the parameters
    for$x(@M){
        $d=0;
        map{if($_==$f||$_==$l){$d++}}split/,/,$B[$x];
        if($d>1){push@D,$x}else{push@S,$x}
    }
}
for(@D){print"Prisoner $_ dies\n"}

# calculate the remaining items
for(@S){push@R,split/,/,$A[$_]}@R=sort{$a<=>$b}grep{!$g{$_}++}@R;

# different set of actors if there were 1 or 2 sets remaining
if(@S>1){@S=($S[0],$m+1..$p,$S[1],0)}else{@S=($m+1..$p)};

$i=0;@B=@D=();
# assign an item to each actor
for$x(@S){
    @T=();
    for($j=0;$j<@R;$j++){
        if($i!=$j){push@T,$R[$j]}
    }$i++;
    print"Prisoner $x drinks @T\n"if$x>0;
    $B[$x]=join",",@T
}

if(!$f||!$l){
    # manual input
    print"Who dies: ";
    $_=<STDIN>;chomp;
    @D=sort split/ /;
    if(@D<@S-1){push@D,0} # because the set that noone drinks isn't manually put in
    %h=map{($_,1)}@D;
    @L=grep{!$h{$_}}(@S);
}
else{
    # calculate who dies based on the parameters
    @D=();
    for$x(@S){
        $d=0;
        map{if($_==$f||$_==$l){$d++}}split/,/,$B[$x];
        if($d>1){push@D,$x}else{push@L,$x}
    }
}

for(@D){print"Prisoner $_ dies\n"if$_>0}

# calculate the remaining items
for(@L){push@F,split/,/,$B[$_]}
map{$c{$_}++}@F;
for(keys%c){push(@Z,$_)if$c{$_}==1}
@R=sort{$a<=>$b}@Z;

print"Suspected bottles: @R"

Teste

$ perl poisened_bottles.pl 20
Prisoners: 20
Bottles: 66
Prisoner 1 drinks 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
Prisoner 2 drinks 1 2 3 4 5 6 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
Prisoner 3 drinks 1 2 3 4 5 6 7 8 9 10 11 12 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
Prisoner 4 drinks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
Prisoner 5 drinks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
Prisoner 6 drinks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
Prisoner 7 drinks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
Prisoner 8 drinks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
Prisoner 9 drinks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 55 56 57 58 59 60 61 62 63 64 65 66
Prisoner 10 drinks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 61 62 63 64 65 66
Prisoner 11 drinks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
Who dies: 2 3 4 5 6 7 8 9 10
Prisoner 2 dies
Prisoner 3 dies
Prisoner 4 dies
Prisoner 5 dies
Prisoner 6 dies
Prisoner 7 dies
Prisoner 8 dies
Prisoner 9 dies
Prisoner 10 dies
Prisoner 1 drinks 2 3 4 5 6 61 62 63 64 65 66
Prisoner 12 drinks 1 3 4 5 6 61 62 63 64 65 66
Prisoner 13 drinks 1 2 4 5 6 61 62 63 64 65 66
Prisoner 14 drinks 1 2 3 5 6 61 62 63 64 65 66
Prisoner 15 drinks 1 2 3 4 6 61 62 63 64 65 66
Prisoner 16 drinks 1 2 3 4 5 61 62 63 64 65 66
Prisoner 17 drinks 1 2 3 4 5 6 62 63 64 65 66
Prisoner 18 drinks 1 2 3 4 5 6 61 63 64 65 66
Prisoner 19 drinks 1 2 3 4 5 6 61 62 64 65 66
Prisoner 20 drinks 1 2 3 4 5 6 61 62 63 65 66
Prisoner 11 drinks 1 2 3 4 5 6 61 62 63 64 66
Who dies: 1 12 14 15 16 17 18 20 11
Prisoner 1 dies
Prisoner 11 dies
Prisoner 12 dies
Prisoner 14 dies
Prisoner 15 dies
Prisoner 16 dies
Prisoner 17 dies
Prisoner 18 dies
Prisoner 20 dies
Suspected bottles: 3 63

Teste sem entrada manual

$ perl poisened_bottles.pl 7 2 5
Prisoners: 7
Bottles: 12
Prisoner 1 drinks 3 4 5 6 7 8 9 10 11 12
Prisoner 2 drinks 1 2 5 6 7 8 9 10 11 12
Prisoner 3 drinks 1 2 3 4 7 8 9 10 11 12
Prisoner 4 drinks 1 2 3 4 5 6 9 10 11 12
Prisoner 5 drinks 1 2 3 4 5 6 7 8 11 12
Prisoner 6 drinks 1 2 3 4 5 6 7 8 9 10
Prisoner 2 dies
Prisoner 4 dies
Prisoner 5 dies
Prisoner 6 dies
Prisoner 1 drinks 2 5 6
Prisoner 7 drinks 1 5 6
Prisoner 3 drinks 1 2 6
Prisoner 1 dies
Suspected bottles: 2 5
LukStorms
fonte
2

Como é tradição, vou postar uma resposta de referência de último lugar.

Python - 7 garrafas

prisoners = int(raw_input())

bottles = 0
while (bottles * (bottles + 1) / 2 - 1) <= prisoners:
    bottles += 1

print bottles

pairs = []
for i in range(bottles):
    for j in range(i + 1, bottles):
        pairs += [str(i + 1) + " " + str(j + 1)]

for i in range(prisoners):
    if i < len(pairs):
        print pairs[i]
    else:
        print

dead_prisoner = raw_input()

for i in range(prisoners):
    print
raw_input() # discard the second day entirely

if dead_prisoner == "":
    print pairs[-1]
else:
    print pairs[int(dead_prisoner) - 1]

Faça com que cada prisioneiro beba um possível par de garrafas, exceto o par dos dois últimos. Se algum prisioneiro morre, o par que esse prisioneiro bebeu foram os envenenados. Caso contrário, foram as duas últimas garrafas que foram envenenadas.

Para lotes de pelo menos n(n-1)/2 - 1prisioneiros, você pode fazer até ngarrafas. Pois n = 7, esse limite inferior é 20.

Na verdade, precisamos apenas de um dia para que esta solução funcione. Uma solução de dois dias com um escopo semelhante pode levar até 20 garrafas N = 20, mas é muito trabalho para uma resposta trivial.

Joe Z.
fonte