|
|
|
![]() |
|
Strumenti |
![]() |
#1 |
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
[Vari] Contest 9: Divisori dei numeri triangolari
Ieri sera mi sono imbattuto in questo problema su un altro sito e l'ho trovato veramente carino. Non è un contest ai livelli di quelli di gugoXX quindi dovreste riuscire ad arrivare alla soluzione finale in poco tempo. Usatelo per staccare un po' e divertirvi
![]() Definizioni L' n-esimo numero triangolare si ottiene sommando tutti i numeri naturali da 1 fino ad n. Quindi il primo numero triangolare è 1, il secondo è 3 e il terzo è 6. Per trovare l'ennesimo numero triangolare senza dover fare la sommatoria ogni volta si può usare la formula di Gauss. Codice:
T(n) = (n * (n + 1)) / 2 Problema Il problema consiste nello scrivere un programma trovi il primo numero triangolare che ha più di x divisori. Codice:
n | Tn | Divisori -------------------------- 1 | 1 | 1 2 | 3 | 1,3 3 | 6 | 1,2,3,6 4 | 10 | 1,2,5,10 5 | 15 | 1,3,5,15 6 | 21 | 1,3,7,21 7 | 28 | 1,2,4,7,14,28 Brute force Codice:
#include <stdlib.h> #include <stdio.h> int count_divisors(int n) { int i, count; for (i = 1, count = 0; i <= n; ++i) if ((n % i) == 0) ++count; return count; } int find_nth_triangular_number(int i) { return (i * (i + 1)) / 2; } int main(int argc, char** argv) { int d, n = 1, i = 1; if (argc != 2) return -1; if ((d = atoi(argv[1])) < 1) return -1; printf("Inizio la ricerca del primo numero triangolare che ha più di %d divisori\n", d); while (count_divisors(n) < d) { n = find_nth_triangular_number(++i); } printf("Si tratta del numero triangolare %d che vale %d\n", i, n); return 0; } Io ho risolto il problema in due modi completamente diversi. Quello più veloce, per fare un paragone con la versione brute force, impiega circa 16 millesimi di secondi per trovare il numero con più di 1000 divisori. Vi allego un po' di output del programma in C così potete fare un po' di confronti per capire se la vostra soluzione è corretta. Codice:
Inizio la ricerca del primo numero triangolare che ha più di 10 divisori Si tratta del numero triangolare 15 che vale 120 Inizio la ricerca del primo numero triangolare che ha più di 50 divisori Si tratta del numero triangolare 224 che vale 25200 Inizio la ricerca del primo numero triangolare che ha più di 100 divisori Si tratta del numero triangolare 384 che vale 73920 Inizio la ricerca del primo numero triangolare che ha più di 200 divisori Si tratta del numero triangolare 2015 che vale 2031120 ![]() Ultima modifica di VICIUS : 11-12-2008 alle 17:54. |
![]() |
![]() |
![]() |
#2 |
Member
Iscritto dal: Sep 2007
Messaggi: 207
|
perdona la domanda VICIUS ma la soluzione l'hai fatta te ihih, il nostro compito qual'e'? fornirne altre più efficienti ?
Ultima modifica di m.distrutti : 11-12-2008 alle 12:51. |
![]() |
![]() |
![]() |
#3 |
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
|
![]() |
![]() |
![]() |
#4 | |
Member
Iscritto dal: Sep 2007
Messaggi: 207
|
Quote:
![]() ![]() Ultima modifica di m.distrutti : 11-12-2008 alle 13:11. |
|
![]() |
![]() |
![]() |
#5 |
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
|
![]() |
![]() |
![]() |
#6 |
Senior Member
Iscritto dal: Jul 2005
Città: Bologna
Messaggi: 1130
|
Ok, fuori concorso... la mia versione brute force (molto "brute" purtroppo...) in factor:
Codice:
USING: kernel math.ranges sequences locals ; :: divisors ( n -- seq ) n dup [1,b] [ n swap mod zero? ] filter nip ; : nth-triangular ( x -- n ) dup 1 + * 2 / ; : count-divisors ( n -- m ) nth-triangular divisors length ; :: find-number ( n x -- m ) n count-divisors x <= [ n 1+ x find-number ] [ n ] if ; : contest-9 ( x -- ) 2 swap find-number . ; Codice:
( scratchpad ) 4 contest-9 7 ( scratchpad ) 100 contest-9 384
__________________
-> The Motherfucking Manifesto For Programming, Motherfuckers |
![]() |
![]() |
![]() |
#7 |
Senior Member
Iscritto dal: Jun 2002
Città: Dublin
Messaggi: 5989
|
Intendevi 1, 3?
__________________
C'ho certi cazzi Mafa' che manco tu che sei pratica li hai visti mai! |
![]() |
![]() |
![]() |
#8 | |
Senior Member
Iscritto dal: Jun 2002
Città: Dublin
Messaggi: 5989
|
Quote:
![]()
__________________
C'ho certi cazzi Mafa' che manco tu che sei pratica li hai visti mai! |
|
![]() |
![]() |
![]() |
#9 |
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Si certo. Ora Correggo.
Si tratta del numero triangolare 41040 che vale 842161320 |
![]() |
![]() |
![]() |
#10 |
Senior Member
Iscritto dal: Jun 2002
Città: Dublin
Messaggi: 5989
|
Propongo questa prima soluzione, è poco più di una brute force. Il tempo impiegato sulla mia macchina per la ricerca del numero triangolare con più di 1000 divisori è spropositato rispetto a quello di VICIUS (ben 26 secondi), quindi proverò a spremerlo il possibile ma temo che dovrò escogitare un metodo più "furbo".
Codice:
#include <stdlib.h> #include <stdio.h> #include <time.h> #include <math.h> #define gauss(n) (unsigned)(((n) * ((n) + 1)) / 2) unsigned divisors(unsigned n) { unsigned count = 2; unsigned i, last; if (n <= 1) { return n; } last = (unsigned) sqrt(n); for (i = 2; i <= last; ++i) { if ((n % i) == 0) { count += 2; } } return count; } unsigned find(unsigned count, unsigned *index, unsigned *number) { unsigned n = count; unsigned sum, d; sum = gauss(n); d = divisors(sum); while (d <= count) { sum += (++n); d = divisors(sum); } (*index) = n; (*number) = sum; return d; } int main(int argc, char *argv[]) { time_t t1, t2; unsigned count; unsigned index; unsigned number; unsigned divisors; --argc; ++argv; if (argc != 1) { fprintf(stderr, "Numero errato di argomenti.\n"); return EXIT_FAILURE; } count = atoi(argv[0]); t1 = clock(); divisors = find(count, &index, &number); t2 = clock(); printf("Ricerca completata in %lf secondi.\n", (double)(t2 - t1) / (double) CLOCKS_PER_SEC); printf("Il numero triangolare cercato sembra essere T%u = %u (con %u divisori).\n", index, number, divisors); return EXIT_SUCCESS; }
__________________
C'ho certi cazzi Mafa' che manco tu che sei pratica li hai visti mai! Ultima modifica di DanieleC88 : 11-12-2008 alle 18:31. |
![]() |
![]() |
![]() |
#11 |
Member
Iscritto dal: Dec 2001
Città: Cernobbio -Co-
Messaggi: 47
|
Per una volta mi lancio anch'io in questi contest.
Ho usato Java, ecco l'implentazione bruce force Codice:
public class TriangularNaive { private static int count_divisors(int n) { int i, count; for (i = 1, count = 0; i <= n; ++i) if ((n % i) == 0) ++count; return count; } private static int find_nth_triangular_number(int i){ return (i * (i + 1)) / 2; } public static void main(String[] args) { int d = 200; int n = 1; int i = 1; long startTime = System.currentTimeMillis(); System.out.println("Inizio la ricerca del primo numero triangolare che ha più di " + d + " divisori\n"); while (count_divisors(n) < d) { n = find_nth_triangular_number(++i); } System.out.println("Si tratta del numero triangolare " + i + " che vale " + n + "\n"); System.out.println("ci ho messo " + (System.currentTimeMillis() - startTime) + " millisecondi"); } } La strada che ho seguito è stata quella di cercare un modo più veloce per sapere quanti divisori ha un numero intero positivo e una possibile risposta si trova questo indirizzo (ultimo punto in fondo). L'idea è che se abbiamo la scomposizione in fattori primi, basta moltiplicare gli esponenti incrementati di uno e otteniamo il numero di divisori. L'implementazione è la seguente (essendo stata fatta di corsissima lo stile lascia un po' a desiderare...) Codice:
public class TriangularSingleThread { private static int countDivisors(int n) { DivisorCounter divisorCounter = new DivisorCounter(n); divisorCounter.decompose(); return divisorCounter.getDivisorCount(); } private static int findNthTriangularNumber(int i){ return (i * (i + 1)) / 2; } public static void main(String[] args) { int d = 1000; int n = 1; int i = 1; long startTime = System.currentTimeMillis(); System.out.println("Inizio la ricerca del primo numero triangolare che ha più di " + d + " divisori\n"); while (countDivisors(n) < d) { n = findNthTriangularNumber(++i); } System.out.println("Si tratta del numero triangolare " + i + " che vale " + n + "\n"); System.out.println("ci ho messo " + (System.currentTimeMillis() - startTime) + " millisecondi"); } private static class DivisorCounter { private int n; private int divisorCount; public DivisorCounter(int n) { this.n = n; this.divisorCount = 1; } private void reduceUsingDivisor(int d) { if (n % d == 0) { int count = 0; while (n % d == 0) { count++; n = n / d; } divisorCount = divisorCount * (count + 1); } } public void decompose() { int d = 2; reduceUsingDivisor(d); d = 3; while (n != 1) { reduceUsingDivisor(d); d = d + 2; } } public int getDivisorCount() { return divisorCount; } } } Ho cercato di spingermi oltre: ho provato istanziando 2 DivisorCounter e passando a ognuno solo uno dei due fattori della formula di Gauss, ma non ho ottenuto miglioramenti di sorta (proverò a lavorarci su un po')
__________________
micheledellatorre.net Ultima modifica di U-Boat : 12-12-2008 alle 11:37. Motivo: errore di battitura |
![]() |
![]() |
![]() |
#12 | |
Senior Member
Iscritto dal: Jun 2002
Città: Dublin
Messaggi: 5989
|
Quote:
![]() P.S.: VICIUS, ma l'agoritmo più efficiente con cui hai misurato i tempi è scritto in C o l'hai fatto in Ruby?
__________________
C'ho certi cazzi Mafa' che manco tu che sei pratica li hai visti mai! |
|
![]() |
![]() |
![]() |
#13 | |
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Quote:
![]() Questa è la mia implementazione di quel algoritmo. Codice:
class Numeric def multiple_of?(n) (self % n) == 0 end def find_factors return [1] if self == 1 n = self factors = Array.new while n.multiple_of? 2 factors.push 2 n /= 2 end factor = 3 while n > 1 if n.multiple_of? factor while n.multiple_of? factor factors.push factor n /= factor end end factor += 2 end factors end def count_divisors return 1 if self == 1 factors = self.find_factors exponents = factors.uniq for i in 0...exponents.size exponents[i] = (factors.select { |f| f == exponents[i] }).size end exponents.map! { |e| e+1 } exponents.inject(1) { |e, tot| tot *= e } end end def contest_9(d) n = i = 1 while n.count_divisors < d i += 1 n += i end "Si tratta del numero triangolare #{i} che vale #{n}" end d = $*[0].to_i puts "Inizio la ricerca del primo numero triangolare che ha piu di #{d} divisori" puts contest_9(d) |
|
![]() |
![]() |
![]() |
#14 | |
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Quote:
![]() |
|
![]() |
![]() |
![]() |
#15 | |
Senior Member
Iscritto dal: Jun 2002
Città: Dublin
Messaggi: 5989
|
Quote:
![]()
__________________
C'ho certi cazzi Mafa' che manco tu che sei pratica li hai visti mai! |
|
![]() |
![]() |
![]() |
#16 |
Senior Member
Iscritto dal: Jul 2005
Città: Bologna
Messaggi: 1130
|
Ho riscritto la mia versione in factor in un modo più idiomatico ed usando il metodo dei fattori primi che avete proposto...
Codice:
! contest-9.factor - versione 2 USING: kernel sequences math.primes.factors ; : count-divisors ( n -- x ) 1 swap group-factors [ second 1+ * ] each ; : nth-triangular ( x -- n ) dup 1 + * 2 / ; : solve-contest ( x n -- ) dup nth-triangular count-divisors pick > [ nip . ] [ 1+ solve-contest ] if ; : contest-9 ( x -- ) 2 solve-contest ;
__________________
-> The Motherfucking Manifesto For Programming, Motherfuckers Ultima modifica di shinya : 12-12-2008 alle 16:33. |
![]() |
![]() |
![]() |
#17 |
Senior Member
Iscritto dal: May 2004
Città: Londra (Torino)
Messaggi: 3692
|
Variazione sul tema, ma sempre li' siamo
(C#) Codice:
Time to calculate all the Primes<10000000 : 650ms First Triangle with more than 300 divisors: 2079th = 2162160 with 320 divisors, in 8ms First Triangle with more than 1000 divisors: 41040th = 842161320 with 1024 divisors, in 276ms
__________________
Se pensi che il tuo codice sia troppo complesso da capire senza commenti, e' segno che molto probabilmente il tuo codice e' semplicemente mal scritto. E se pensi di avere bisogno di un nuovo commento, significa che ti manca almeno un test. |
![]() |
![]() |
![]() |
#18 |
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
|
![]() |
![]() |
![]() |
#19 |
Senior Member
Iscritto dal: May 2004
Città: Londra (Torino)
Messaggi: 3692
|
I tempi vanno su...
Codice:
Time to calculate all the Primes<10000000 : 695ms First Triangle with more than 300 divisors: 2079th = 2162160 with 320 divisors, in 8ms First Triangle with more than 1000 divisors: 41040th = 842161320 with 1024 divisors, in 245ms First Triangle with more than 2000 divisors: 395199th = 78091322400 with 2880 divisors, in 1416ms First Triangle with more than 10000 divisors: 73361375th = 2690945707626000 with 12800 divisors, in 41241ms
__________________
Se pensi che il tuo codice sia troppo complesso da capire senza commenti, e' segno che molto probabilmente il tuo codice e' semplicemente mal scritto. E se pensi di avere bisogno di un nuovo commento, significa che ti manca almeno un test. |
![]() |
![]() |
![]() |
#20 |
Senior Member
Iscritto dal: May 2004
Città: Londra (Torino)
Messaggi: 3692
|
E poi scendono giu' (poco)
Basta, mi fermo qui Codice:
Time to calculate all the Primes<10000000 : 681ms First Triangle with more than 300 divisors: 2079th = 2162160 with 320 divisors, in 8ms First Triangle with more than 1000 divisors: 41040th = 842161320 with 1024 divisors, in 195ms First Triangle with more than 2000 divisors: 395199th = 78091322400 with 2880 divisors, in 835ms First Triangle with more than 10000 divisors: 73361375th = 2690945707626000 with 12800 divisors, in 29774ms Codice:
class Program { static void Main(string[] args) { int MaxPrim = 10000000; Stopwatch sw = new Stopwatch(); sw.Start(); Decompos dc = new Decompos(MaxPrim); sw.Stop(); Console.WriteLine("Time to calculate all the Primes<{0} : {1}ms", MaxPrim, sw.ElapsedMilliseconds); sw.Reset(); int ndiv; ndiv=300; Console.WriteLine("First Triangle with more than {0} divisors: ", ndiv); sw.Start(); var res = dc.Striang(ndiv); sw.Stop(); Console.WriteLine("{0}th = {1} with {2} divisors, in {3}ms", res.TriangleOrder, res.Value, res.NDivisors, sw.ElapsedMilliseconds); Console.WriteLine(); sw.Reset(); ndiv = 1000; Console.WriteLine("First Triangle with more than {0} divisors: ", ndiv); sw.Start(); res = dc.Striang(ndiv); sw.Stop(); Console.WriteLine("{0}th = {1} with {2} divisors, in {3}ms", res.TriangleOrder, res.Value, res.NDivisors, sw.ElapsedMilliseconds); Console.WriteLine(); sw.Reset(); ndiv = 2000; Console.WriteLine("First Triangle with more than {0} divisors: ", ndiv); sw.Start(); res = dc.Striang(ndiv); sw.Stop(); Console.WriteLine("{0}th = {1} with {2} divisors, in {3}ms", res.TriangleOrder, res.Value, res.NDivisors, sw.ElapsedMilliseconds); Console.WriteLine(); sw.Reset(); ndiv = 10000; Console.WriteLine("First Triangle with more than {0} divisors: ", ndiv); sw.Start(); res = dc.Striang(ndiv); sw.Stop(); Console.WriteLine("{0}th = {1} with {2} divisors, in {3}ms", res.TriangleOrder, res.Value, res.NDivisors, sw.ElapsedMilliseconds); Console.WriteLine(); sw.Reset(); Console.ReadKey(); } } public class Decompos { public Decompos(int maxprim) { Primes = Eratostene.GetPrimes(maxprim).ToArray(); } private int[] Primes; public class Result { public int TriangleOrder; public long Value; public int NDivisors; public Dictionary<long, int> Factors; } public Result Striang(int ndiv) { if (ndiv < 700) return StriangSingleCore(ndiv); return StriangDualCore(ndiv); } public Result StriangSingleCore(int ndiv) { long ctriang = 0; for (int t = 1; ; t++) { ctriang += (long)t; var Fact = Factorize(ctriang, ndiv); if (Fact != null) { Fact.TriangleOrder = t; return Fact; } } } private Result Stepper(int findex, int lindex, int ndiv) { for (int u = findex; u < lindex; u++) { long longu = (long)u; long ctriang = (longu * (longu + 1)) / 2; var Fact = Factorize(ctriang, ndiv); if (Fact != null) { Fact.TriangleOrder = u; return Fact; } } return null; } private Result StriangDualCore(int ndiv) { int splitter = 5000; int bisplitter = splitter + splitter; for (int t = 1; ; t += bisplitter) { var r1 = Future<Result>.Create(() => Stepper(t, t + splitter, ndiv)); var r2 = Future<Result>.Create(() => Stepper(t + splitter + 1, t + bisplitter, ndiv)); Result r1res = r1.Value; if (r1res != null) return r1res; Result r2res = r2.Value; if (r2res != null) return r2res; } } private Result Factorize(long input, int MinDivisors) { int PrimesLength = Primes.Length; long rem=input; Dictionary<long, int> ret = new Dictionary<long, int>(); int IndexPrimo=0; int CurrentNDivisors = 1; do { if (IndexPrimo >= PrimesLength) throw new Exception("Primes Table too small"); long Primo = Primes[IndexPrimo]; int expected = (int)(rem / Primo) + 1; if ((CurrentNDivisors * expected ) < MinDivisors) return null; int Esponente = 0; while ((rem % Primo) == 0) { Esponente++; rem /= Primo; } if (Esponente != 0) { ret[Primo] = Esponente; CurrentNDivisors = CurrentNDivisors * (Esponente + 1); } IndexPrimo++; } while (rem != 1); if (CurrentNDivisors <= MinDivisors) return null; Result res = new Result(); res.Value = input; res.NDivisors = CurrentNDivisors; res.Factors = ret; return res; } } public static class Eratostene { public static IEnumerable<int> GetPrimes(int maxvalue) { int[] Buff = new int[maxvalue]; int mx = (int)Math.Sqrt((double)maxvalue)+1; int curprimo=2; do { yield return curprimo; for (int t = curprimo; t < maxvalue; t += curprimo) { Buff[t] = 1; } do { curprimo++; } while ((Buff[curprimo] != 0) && (curprimo < mx)); } while (curprimo < mx); for (int t = curprimo; t < maxvalue; t++) { if (Buff[t] == 0) yield return t; } } }
__________________
Se pensi che il tuo codice sia troppo complesso da capire senza commenti, e' segno che molto probabilmente il tuo codice e' semplicemente mal scritto. E se pensi di avere bisogno di un nuovo commento, significa che ti manca almeno un test. |
![]() |
![]() |
![]() |
Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 06:43.