Il 08/04/2014 02:18, Tommaso Russo, Trieste ha scritto:
> Il 07/04/2014 17:36, Maurizio Frigeni ha scritto:
>> Marco Gavanelli <
ma...@spammati.da.solo.com> wrote:
>>
>>> Il numero del tram e` il 12.
>> Giusto! La stessa risposta è stata data anche da Tommaso Russo, 6 minuti
>> prima di te. Riporto qui sotto la tua soluzione
> Questo mi obbliga moralmente a spiegare come ho fatto ad arrivarci io :-)
> Alla fine, non ho usato un metodo molto diverso da quello di Marco
> Gavanelli, ma come sono arrivato al metodo puo' avere qualche interesse.
Quando Paolo Lucchesi ha risposto: "13. Età 36, 3 figli, di età 6, 6, 1
o 9, 2, 2" e Maurizio Frigeni ha risposto "no", ho capito che
evidentemente 13 poteva essere ottenuto come somma anche da *altre due*
addizioni con lo stesso numero di addendi (numero figli) e lo stesso
prodotto degli addendi, per cui B non avrebbe potuto rispondere "Aha!
ADESSO so quanti anni hai!"; e che quindi bisognava trovare un altro
numero (di tram) tale che, fra tutte le possibili addizioni che lo danno
come somma, ce ne fossero *solo due* con lo stesso numero di addendi e
lo stesso prodotto.
Mi sono messo quindi all'opera per scrivere una routine che, data una
somma m e un numero di addendi n, producesse in output tutte le
addizioni di n addendi con m come somma, nonche' il prodotto degli
addendi; facendo poi variare m da 1 a quanto sarebbe bastato, e, per
ogni m, n da 1 a m, avrei ottenuto vettori di prodotti fra cui cercare
visivamente uno con due componenti eguali (e, una volta trovatolo,
verificare che non ce ne fosse un altro per lo stesso m. Avrei trovato
cosi' *una* soluzione al problema - non sapevo ancora che ce ne fosse
una sola).
Ora, generare tutte le addizioni che danno m come somma [considerando
"la stessa addizione" tutte quelle ottenibili l'una dall'altra con la
proprieta' commutativa (e si puo' scegliere, a rappresentarla, quella in
cui gli addendi sono in ordine decrescente, es. 6+3+1)] e' abbastanza
semplice: detta A(m,n) una addizione di somma m e primo (e massimo)
addendo n, basta concatenare ricorsivamente, per ogni n fra 1 ed m, la
stringa "n" con tutte le stringhe della forma " + A(n-m,p)", dove p
varia fra 1 e max(m-n,n). La stop condition e' data dal fatto che non
esiste alcuna A(0,p).
In questo modo, pero', avrei ottenuto le addizioni in ordine di *massimo
addendo*, mentre per poter ispezionare rapidamente i prodotti mi serviva
ottenerle in ordine di *numero di addendi*. Ero gia' rassegnato a
scriverle tutte in memoria e poi riordinarle, quando, spigolando
Internet, sono arrivato a questa prima lettura consigliata:
<
http://theory.cs.uvic.ca/inf/nump/NumPartition.html>
contenente un bel teoremino: data la somma m, ad ogni addizione, di
massimo addendo n, corrisponde una e una sola addizione coniugata, di n
addendi. La dimostrazione, banalissima, e l'algoritmo per ottenere da
un'addizione la sua coniugata, si intuiscono facilmente esaminando i
diagrammi di Ferrers di due addizioni coniugate (seconda e terza
immagine a quadratini rossi nella figura).
A questo punto pero', prima di mettermi a scrivere, ho riflettuto: "ma
possibile che nessuno si sia posto, e abbia risolto, il problema di
trovare tutte le addizioni di n addendi di somma m?" Ed ho ricominciato
a spigolare, giungendo a questo interessante thread:
<
http://www.ioprogrammo.it/index.php?topic=18147.0>
il terzo e quinto post, dell'utente M.A.W. 1968, contengono alcuni link
fra i quali suggerisco questa lettura:
<
http://forum.masterdrive.it/blogs/m-a-w-1968/problemino-addizioni-67/>
ma sopratutto questo:
<
http://www.math.mtu.edu/~kreher/cages/Src.html>
ad esempi di codice che implementano gli algoritmi descritti in
"Combinatorial Algorithms" di Donald L. Kreher e Douglas Stinson, CRC
press, 1999.
Bingo! Il primo file zippato, GEN.tar, nel file IntegerPartitions.c,
conteneva (algoritmo 3.7) esattamente quello che mi stavo mettendo a
scrivere. Mi sono limitato a modificarlo, cancellando l'inutile,
inserendo la chiamata in un doppio loop
for(m=1;m<=40;m=m+1) { for(n=1;n<=m;n=n+1) { ... } }
e calcolando e aggiungendo alla printf il prodotto. Il risultato lo
riporto in calce(*).
Fatto girare i programma e salvato l'output, ho potuto facilmente
scorrere i gruppi di prodotti (allineati) fino a trovare, per m=12,
Test of Algorithm 3.7 with m=12 n=4
12 = 9+1+1+1 prod = 9
12 = 8+2+1+1 prod = 16
12 = 7+3+1+1 prod = 21
12 = 6+4+1+1 prod = 24
12 = 5+5+1+1 prod = 25
12 = 7+2+2+1 prod = 28
12 = 6+3+2+1 prod = 36
12 = 5+4+2+1 prod = 40
12 = 5+3+3+1 prod = 45
12 = 4+4+3+1 prod = 48 <<<---
12 = 6+2+2+2 prod = 48 <<<---
12 = 5+3+2+2 prod = 60
12 = 4+4+2+2 prod = 64
12 = 4+3+3+2 prod = 72
12 = 3+3+3+3 prod = 81
e nessun altro gruppo con un prodotto duplicato.
Devo pero' dire che questo genere di problemi, in cui gli interlocutori
deducono logicamente ma *in pochi secondi* ("Aha! ADESSO so quanti anni
hai!") una proprieta' vera, ma dimostrabile solo con un procedimento
enumerativo che richiede migliaia di cicli di CPU, mi sono sempre
sembrati un po' una presa in giro... e per portare la presa in giro
all'estremo, vi propongo il piu' difficile dei problemi del genere che
sono riuscito a spigolare:
<
http://utenti.quipo.it/base5/numeri/probimpos.htm>
Warning: la pagina contiene anche la soluzione, per cui, se volete
cimentarvi, non leggete oltre il problema n.6.
A chi alla fine la soluzione se la andra' a vedere, chiedo: ma vi pare
credibile che quel ragionamento venga fatto dai due prof. nel tempo del
colloquio riportato nella narrazione? ;-)
(*)
------------------------------------------------------------
/*
** partitions.c
*/
/*
** October 1, 1997
** This program implemented Algorithm 3.1 - 3.9
** from "Combinatorial Algorithms"
** Donald L. Kreher - Douglas Stinson
** CRC press, 1999
**
** Modified by TRu-TS on april 7, 2014
** for another purpose
**
*/
#include<stdio.h>
#include<stdlib.h>
#define false 0;
#define true 1;
#define Min(x,y) ((x<y)?x:y)
typedef struct partitionnode
{
int m;
int numparts;
int parts[101];
} partition;
int Pn[40][40];
int P[50];
void output(partition a)
/*
** print out the partition a
*/
{
int i,j, prod;
printf("%d = ",a.m);
prod = 1;
for(i=1;i<=a.numparts;i=i+1)
{
j = a.parts[i];
prod = j*prod;
printf("%d",j);
if (i != a.numparts)
printf("+"); else printf(" prod = %d",prod);
}
}
void RecPartition(partition a, int m, int B, int N)
/*
** See Algorithm 3.1
** generate all partitions with largest part of size at most B
** recursive procedure used in other procedures
*/
{
int i;
if(m==0)
{
a.numparts = N;
output(a); printf("\n");
}
else
{
for (i=1;i<=Min(B,m);i=i+1)
{
a.parts[N+1] = i;
RecPartition(a,m-i,i,N+1);
}
}
}
void EnumPartitions(int m, int n )
/*
** Algorithm 3.5
** compute the partition numbers P[i] and P[i,j] for i <= m, j <= n
*/
{
int i,j;
Pn[0][0] = 1;
P[0] = 1;
for(i=1;i<=m;i=i+1) Pn[n][0]=0;
for(i=1;i<=m;i=i+1)
{
for (j=1;j<=Min(i,m);j=j+1)
{
if (i < (2*j))
Pn[i][j] = Pn[i-1][j-1];
else
Pn[i][j] = Pn[i-1][j-1] + Pn[i-j][j];
P[i] = P[i] + Pn[i][j];
}
P[i] = 0;
for(j=1;j<=m;j++) P[i] = P[i] + Pn[i][j];
}
}
int PartitionLexRank(int m,int n,partition a)
/*
** Algorithm 3.8
** find the rank of partition a,
** where a is given in standard form
*/
{
int i,r;
partition b;
EnumPartitions(m,n);
b = a;
m = b.m;
n = b.numparts;
r = 0;
while (m > 0)
{
if (b.parts[n] == 1)
{
m = m-1;
n = n-1;
}
else
{
for(i=1;i<=n;i=i+1)
b.parts[i] = b.parts[i] - 1;
r = r + Pn[m-1][n-1];
m = m-n;
}
}
return(r);
}
void PartitionLexSuccessor(int m,int n,partition *a,int *flag)
/*
** Algorithm 3.7
** replaces the partition a by its successor,
** where a is given in standard form
*/
{
int i,j,d;
i = 2;
while( (i<=n) && ((*a).parts[1]<=((*a).parts[i]+1)) )
i=i+1;
if(i == (n+1))
{
*flag = false;
}
else
{
(*a).parts[i] = (*a).parts[i] + 1;
d = -1;
for(j=(i-1);j>=2;j=j-1)
{
d = d + (*a).parts[j] - (*a).parts[i];
(*a).parts[j] = (*a).parts[i];
}
(*a).parts[1] = (*a).parts[1] + d;
}
}
int main()
{
int m,n,i,j,r,s,num;
int flag;
partition a;
char junk;
for (m=1;m<=40;m=m+1) {
for (n=1;n<=m;n=n+1) {
printf("\nTest of Algorithm 3.7 with m=%d n=%d \n",m,n);
a.m = m;
a.numparts = n;
a.parts[1] = m-n+1;
for(i=2;i<=n;i=i+1)
a.parts[i] = 1;
flag = true;
while(flag)
{
output(a);
printf("\n");
r=PartitionLexRank(m,n,a);
PartitionLexSuccessor(m,n,&a,&flag);
}
}
printf("\nEnd\n");
}
return(0);
}
------------------------------------------------------------