Conta il numero di decimali pesanti tra 2 numeri


16

Diciamo che abbiamo un numero intero non negativo che è "pesante" (cioè "pesante") se il suo valore medio delle cifre è maggiore di 7.

Il numero 6959 è "pesante" perché:

(6 + 9 + 5 + 9) / 4 = 7.5

Il numero 1234 non è, perché:

(1 + 2 + 3 + 4) / 4 = 2.5

Scrivi una funzione, in qualsiasi lingua,

HeftyDecimalCount(a, b)

che, quando fornito due numeri interi positivi aeb restituisce un numero intero che indica quanti numeri interi "pesanti" sono compresi nell'intervallo [a..b], inclusi.

Ad esempio, dato a = 9480 e b = 9489:

9480   (9+4+8+0)/4 21/4 = 5.25 
9481   (9+4+8+1)/4 22/4 = 5.5
9482   (9+4+8+2)/4 23/4 = 5.75  
9483   (9+4+8+3)/4 24/4 = 6    
9484   (9+4+8+4)/4 25/4 = 6.25     
9485   (9+4+8+5)/4 26/4 = 6.5 
9486   (9+4+8+6)/4 27/4 = 6.75  
9487   (9+4+8+7)/4 28/4 = 7
9488   (9+4+8+8)/4 29/4 = 7.25   hefty 
9489   (9+4+8+9)/4 30/4 = 7.5    hefty

Due dei numeri in questo intervallo sono "pesanti" e quindi la funzione dovrebbe restituire 2.

Alcune linee guida:

  • supponiamo che né aob non superino 200.000.000.
  • una soluzione n-quadrata funzionerà, ma sarà lenta: qual è la più veloce che possiamo risolvere?

2
cosa ha gettato il TIMEOUT?

Risposte:


11

Il problema può essere risolto in O (polilogo (b)).

Definiamo f(d, n)il numero di numeri interi con cifre decimali fino a d con somma delle cifre inferiore o uguale a n. Si può vedere che questa funzione è data dalla formula

f (d, n)

Deriviamo questa funzione, iniziando con qualcosa di più semplice.

h (n, d) = \ binom {n + d-1} {d-1} = \ binom {(n + 1) + (d-1) -1} {d-1}

La funzione h conta il numero di modi per scegliere d - 1 elementi da un set multiplo contenente n + 1 elementi diversi. È anche il numero di modi per suddividere n in d bin, che può essere facilmente visto costruendo d - 1 recinti attorno a n uno e riassumendo ogni sezione separata. Esempio per n = 2, d = 3 ':

3-choose-2     fences        number
-----------------------------------
11             ||11          002
12             |1|1          011
13             |11|          020
22             1||1          101
23             1|1|          110
33             11||          200

Quindi, h conta tutti i numeri con una somma di cifre di cifre n e d. Tranne che funziona solo per n meno di 10, poiché le cifre sono limitate a 0 - 9. Per risolvere questo problema per i valori 10 - 19, è necessario sottrarre il numero di partizioni che hanno un cestino con un numero maggiore di 9, che chiamerò bin overflow da ora in poi.

Questo termine può essere calcolato riutilizzando h nel modo seguente. Contiamo il numero di modi per partizionare n - 10, quindi scegliamo uno dei bin in cui inserire il 10, il che si traduce nel numero di partizioni con un cestino overflow. Il risultato è la seguente funzione preliminare.

g (n, d) = \ binom {n + d-1} {d-1} - \ binom {d} {1} \ binom {n + d-1-10} {d-1}

Continuiamo in questo modo per n minore o uguale a 29, contando tutti i modi di partizionare n - 20, quindi scegliendo 2 bin in cui inserire i 10, contando così il numero di partizioni contenenti 2 bin overflow.

Ma a questo punto dobbiamo stare attenti, perché abbiamo già contato le partizioni con 2 bidoni overflow nel termine precedente. Non solo, ma in realtà li abbiamo contati due volte. Facciamo un esempio e diamo un'occhiata alla partizione (10,0,11) con la somma 21. Nel termine precedente, abbiamo sottratto 10, calcolato tutte le partizioni dei restanti 11 e messo il 10 in uno dei 3 bin. Ma questa particolare partizione può essere raggiunta in due modi:

(10, 0, 1) => (10, 0, 11)
(0, 0, 11) => (10, 0, 11)

Dal momento che abbiamo contato anche queste partizioni una volta nel primo termine, il conteggio totale delle partizioni con 2 contenitori in overflow è pari a 1 - 2 = -1, quindi dobbiamo contarle ancora una volta aggiungendo il termine successivo.

g (n, d) = \ binom {n + d-1} {d-1} - \ binom {d} {1} \ binom {n + d-1-10} {d-1} + \ binom { d} {2} \ binom {n + d-1-20} {d-1}

Ripensandoci un po 'di più, scopriamo presto che il numero di volte in cui una partizione con un numero specifico di bin overflow viene contata in un termine specifico può essere espressa dalla seguente tabella (colonna i rappresenta il termine i, riga j partizioni con j overflown bidoni).

1 0 0 0 0 0 . .
1 1 0 0 0 0 . .
1 2 1 0 0 0 . .
1 4 6 4 1 0 . .
. . . . . . 
. . . . . . 

Sì, è il triangolo di Pascal. L'unico conteggio a cui siamo interessati è quello nella prima riga / colonna, ovvero il numero di partizioni con zero bin overflow. E poiché la somma alternata di ogni riga ma la prima è uguale a 0 (ad esempio 1 - 4 + 6 - 4 + 1 = 0), è così che ci liberiamo di loro e arriviamo alla penultima formula.

g (n, d) = \ sum_ {i = 0} ^ {d} (-1) ^ i \ binom {d} {i} \ binom {n + d-1 - 10i} {d-1}

Questa funzione conta tutti i numeri con cifre d con una somma di cifre pari a n.

Ora, che dire dei numeri con cifra-somma inferiore a n? Possiamo usare una ricorrenza standard per i binomi più un argomento induttivo, per dimostrarlo

\ bar {h} (n, d) = \ binom {n + d} {d} = \ binom {n + d-1} {d-1} + \ binom {n + d-1} {d} = h (n, d) + \ bar {h} (n-1, d)

conta il numero di partizioni con somma cifre al massimo n. E da questo f si può derivare usando gli stessi argomenti di g.

Usando questa formula, possiamo ad esempio trovare il numero di numeri pesanti nell'intervallo da 8000 a 8999 poiché 1000 - f(3, 20), poiché ci sono migliaia di numeri in questo intervallo, e dobbiamo sottrarre il numero di numeri con somma delle cifre inferiore o uguale a 28 tenendo conto del fatto che la prima cifra contribuisce già 8 alla somma delle cifre.

Come esempio più complesso, diamo un'occhiata al numero di numeri pesanti nell'intervallo 1234..5678. Possiamo prima andare da 1234 a 1240 a passi di 1. Quindi andiamo da 1240 a 1300 a passi di 10. La formula sopra ci dà il numero di numeri pesanti in ciascuno di tali intervalli:

1240..1249:  10 - f(1, 28 - (1+2+4))
1250..1259:  10 - f(1, 28 - (1+2+5))
1260..1269:  10 - f(1, 28 - (1+2+6))
1270..1279:  10 - f(1, 28 - (1+2+7))
1280..1289:  10 - f(1, 28 - (1+2+8))
1290..1299:  10 - f(1, 28 - (1+2+9))

Ora andiamo dal 1300 al 2000 a passi di 100:

1300..1399:  100 - f(2, 28 - (1+3))
1400..1499:  100 - f(2, 28 - (1+4))
1500..1599:  100 - f(2, 28 - (1+5))
1600..1699:  100 - f(2, 28 - (1+6))
1700..1799:  100 - f(2, 28 - (1+7))
1800..1899:  100 - f(2, 28 - (1+8))
1900..1999:  100 - f(2, 28 - (1+9))

Da 2000 a 5000 con incrementi di 1000:

2000..2999:  1000 - f(3, 28 - 2)
3000..3999:  1000 - f(3, 28 - 3)
4000..4999:  1000 - f(3, 28 - 4)

Ora dobbiamo ridurre nuovamente le dimensioni del gradino, passando da 5000 a 5600 a passi di 100, da 5600 a 5670 a passi di 10 e infine da 5670 a 5678 a passi di 1.

Un esempio di implementazione di Python (che nel frattempo ha ricevuto lievi ottimizzazioni e test):

def binomial(n, k):
    if k < 0 or k > n:
        return 0
    result = 1
    for i in range(k):
        result *= n - i
        result //= i + 1
    return result

binomial_lut = [
    [1],
    [1, -1],
    [1, -2, 1],
    [1, -3, 3, -1],
    [1, -4, 6, -4, 1],
    [1, -5, 10, -10, 5, -1],
    [1, -6, 15, -20, 15, -6, 1],
    [1, -7, 21, -35, 35, -21, 7, -1],
    [1, -8, 28, -56, 70, -56, 28, -8, 1],
    [1, -9, 36, -84, 126, -126, 84, -36, 9, -1]]

def f(d, n):
    return sum(binomial_lut[d][i] * binomial(n + d - 10*i, d)
               for i in range(d + 1))

def digits(i):
    d = map(int, str(i))
    d.reverse()
    return d

def heavy(a, b):
    b += 1
    a_digits = digits(a)
    b_digits = digits(b)
    a_digits = a_digits + [0] * (len(b_digits) - len(a_digits))
    max_digits = next(i for i in range(len(a_digits) - 1, -1, -1)
                      if a_digits[i] != b_digits[i])
    a_digits = digits(a)
    count = 0
    digit = 0
    while digit < max_digits:
        while a_digits[digit] == 0:
            digit += 1
        inc = 10 ** digit
        for i in range(10 - a_digits[digit]):
            if a + inc > b:
                break
            count += inc - f(digit, 7 * len(a_digits) - sum(a_digits))
            a += inc
            a_digits = digits(a)
    while a < b:
        while digit and a_digits[digit] == b_digits[digit]:
            digit -= 1
        inc = 10 ** digit
        for i in range(b_digits[digit] - a_digits[digit]):
            count += inc - f(digit, 7 * len(a_digits) - sum(a_digits))
            a += inc
            a_digits = digits(a)
    return count

Modifica : ha sostituito il codice con una versione ottimizzata (che sembra persino più brutta del codice originale). Ho anche risolto alcuni casi angolari mentre ero lì. heavy(1234, 100000000)impiega circa un millisecondo sulla mia macchina.


Salve, questa soluzione funziona ed è stato un calcolo corretto, tuttavia il limite di tempo per piccoli numeri era di soli 0,10 secondi e il limite di tempo per grandi numeri era di 0,35 secondi. Il codice sopra che hai pubblicato ha impiegato circa 1 secondo. Pensi che ci sia un modo migliore e un modo intelligente di gestirlo, in modo tale da saltare alcuni numeri perché sappiamo già che quel determinato numero avrebbe una cifra inferiore a 7? O forse se esiste un modo più intelligente di gestirlo? Per tua informazione, questa domanda è stata anche contrassegnata come una domanda difficile.

1
@Bob: il codice è scritto in Python e non è affatto ottimizzato. Se vuoi che sia veloce, scrivilo in C. Ma anche in Python puro c'è molto margine di miglioramento. La prima cosa che necessita di ottimizzazione è la binomial()funzione. Ci sono anche alcune altre cose che possono essere facilmente migliorate. Pubblicherò un aggiornamento tra pochi minuti.
Sven Marnach,

Oppure possiamo semplicemente usare una tabella di ricerca con f pre-calcolata (m, n). Dato che 200.000.000 è il limite, l'utilizzo della memoria dovrebbe essere minimo. (Hai già il mio +1).

@Moron: Quella sembra certamente essere l'opzione migliore - ci proverò.
Sven Marnach,

@Moron: avrei bisogno di includere la tabella di ricerca nel codice sorgente. Di solito f(d, n)non viene chiamato due volte con gli stessi parametri durante un'esecuzione del programma.
Sven Marnach,

5

Recurse e usa permutazioni.

Supponiamo di definire una funzione generale che trova i valori tra aeb con una pesantezza superiore a x:

heavy_decimal_count(a,b,x)

Con il tuo esempio da a = 8675 a b = 8689, la prima cifra è 8, quindi gettala via: la risposta sarà la stessa di 675-689 e di nuovo da 75 a 89.

Il peso medio delle prime due cifre 86 è 7, quindi le cifre rimanenti richiedono un peso medio superiore a 7 per qualificarsi. Quindi, la chiamata

heavy_decimal_count(8675,8689,7)

è equivalente a

heavy_decimal_count(75,89,7)

Quindi il nostro intervallo per la (nuova) prima cifra va da 7 a 8, con queste possibilità:

7: 5-9
8: 0-9

Per 7, abbiamo ancora bisogno di una media di oltre 7, che può provenire solo da una cifra finale di 8 o 9, dandoci 2 possibili valori.

Per 8, abbiamo bisogno di una media di oltre 6, che può provenire solo da una cifra finale di 7-9, dandoci 3 possibili valori.

Quindi, 2 + 3 produce 5 possibili valori.

Quello che sta succedendo è che l'algoritmo inizia con il numero di 4 cifre e lo divide in problemi più piccoli. La funzione si chiamerebbe ripetutamente con versioni più semplici del problema fino a quando non ha qualcosa che può gestire.


2
Quindi stai rivendicando Heavy (886.887) = Heavy (6,7)?

@Moron: No, perché i primi due 8 cambiano la soglia di pesantezza. Nell'esempio, i primi due erano 86, che in media a 7 e quindi non cambiano la soglia. Se (8 + 8 + x) / 3> 7, quindi x> 5. So Heavy (886.887,7,0) == Heavy (6,7,5,0).

@Phil H, non penso che questa idea così com'è funzionerebbe: se prendi 9900 e 9999, la altererebbe in modo che i pesanti tra 0 e 99, prendendo in considerazione 8 ad esempio e 9908 non sia un numero pesante ( @Aryabhatta).
Hans Roggeman,

3

Forse puoi saltare molti candidati nell'intervallo dalla a alla b accumulando la loro "pesantezza".

se conosci la lunghezza del tuo numero sai che ogni cifra può cambiare la pesantezza di solo 1 / lunghezza.

Quindi, se inizi da un numero che non è pesante, dovresti essere in grado di calcolare il numero successivo che sarà pesante, se li aumenti di uno.

Nel tuo esempio sopra a partire da 8680 avg = 5,5, che è 7-5,5 = 1,5 punti di distanza dal bordo della pesantezza, sapresti che ci sono 1,5 / (1/4) = 6 numeri in mezzo, che NON sono pesanti.

Questo dovrebbe essere il trucco!


Lo stesso vale per una fila di numeri "pesanti". Puoi semplicemente calcolare il numero e saltarlo!

1
Basta moltiplicare tutto per il numero di cifre e ti libererai di quelle fastidiose /length.

1

Che ne dici di una semplice funzione ricorsiva? Per semplificare le cose, calcola tutti i numeri pesanti con digitscifre e una somma minima di cifre min_sum.

int count_heavy(int digits,int min_sum) {
  if (digits * 9 < min_sum)//impossible (ie, 2 digits and min_sum=19)
    return 0; //this pruning is what makes it fast

  if (min_sum <= 0)
      return pow(10,digits);//any digit will do,
      // (ie, 2 digits gives 10*10 possibilities)

  if (digits == 1)
  //recursion base
    return 10-min_sum;//only the highest digits

  //recursion step
  int count = 0;
  for (i = 0; i <= 9; i++)
  {
     //let the first digit be i, then
     count += count_heavy(digits - 1, min_sum - i);
  }
  return count;
}

count_heavy(9,7*9+1); //average of 7,thus sum is 7*9, the +1 is 'exceeds'.

Implementato questo in Python e ha trovato tutti i numeri pesanti a 9 cifre in ~ 2 secondi. Un po 'di programmazione dinamica potrebbe migliorare questo.


0

Questa è una possibile soluzione.

public int heavy_decimal_count(int A, int B)
{
    int count = 0;                       
    for (int i = A; i <= B; i++)
    {
        char[] chrArray = i.ToString().ToCharArray();
        float sum = 0f;
        double average = 0.0f;
        for (int j = 0; j < chrArray.Length; j++)
        {
            sum = sum + (chrArray[j] - '0');                   
        }
        average = sum / chrArray.Length;                
        if (average > 7)
            count++;
    }
    return count;
}

1
Benvenuti in Code Golf. Quando una domanda ha già una risposta, sono benvenute più risposte se sono migliori di una in uno dei criteri vincenti o mostrano un modo nuovo e interessante per rispondervi. Non vedo neanche la tua risposta.
ugoren,

0

C, per l'intervallo [a, b] è O (ba)

c(n,s,j){return n?c(n/10,s+n%10,j+1):s>7*j;}

HeftyDecimalCount(a,b){int r; for(r=0;a<=b;++a)r+=c(a,0,0); return r;}

//l'esercizio

main()
{
 printf("[9480,9489]=%d\n", HeftyDecimalCount(9480,9489));
 printf("[0,9489000]=%d\n", HeftyDecimalCount(9480,9489000));
 return 0;
}

//i risultati

//[9480,9489]=2
//[0,9489000]=66575

Cosa significa "scappatoie standard"?
RosLuP

1
@Riker Qui il tag non è <codegolf> è <algoritmo veloce>
RosLuP
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.