Порахуйте кількість здоровенних десяткових знаків між 2 числами


16

Скажімо, у нас є негативне ціле число, яке є "здоровенним" (тобто "важким"), якщо його середнє значення цифри перевищує 7.

Число 6959 "здоровенне", оскільки:

(6 + 9 + 5 + 9) / 4 = 7,5

Число 1234 немає, тому що:

(1 + 2 + 3 + 4) / 4 = 2,5

Напишіть функцію будь-якою мовою,

HeftyDecimalCount(a, b)

яке при наданні двох натуральних чисел a і b повертає ціле число, що вказує, скільки "здоровенних" цілих чисел знаходиться в інтервалі [a..b] включно.

Наприклад, задані a = 9480 і 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

Два з числа в цьому діапазоні є "здоровенними", тому функція повинна повертати 2.

Деякі вказівки:

  • припустимо, що ні а, ні b не перевищує 20000000000.
  • рішення n-квадрата буде працювати, але буде повільним - що найшвидше ми можемо вирішити?

2
що кинув ЧАС?

Відповіді:


11

Задачу можна вирішити в O (полілог (b)).

Ми визначаємо f(d, n)як число цілих чисел до d десяткових цифр із сумою цифр менше або дорівнює n. Видно, що ця функція задана формулою

f (d, n)

Виведемо цю функцію, починаючи з чогось більш простого.

h (n, d) = \ біном {n + d-1} {d-1} = \ біном {(n + 1) + (d-1) -1} {d-1}

Функція h підраховує кількість способів вибору d - 1 елементів з множини, що містить n + 1 різних елементів. Це також кількість способів розділити n на d бункери, що можна легко побачити, будуючи d - 1 парканів навколо n і підсумовуючи кожен відокремлений відрізок. Приклад для 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

Отже, h підраховує всі числа, що мають розрядну суму з n та d цифр. За винятком того, що він працює лише для n менше 10, оскільки цифри обмежені 0 - 9. Для того, щоб зафіксувати це для значень 10 - 19, нам потрібно відняти кількість розділів, що мають один відро з числом, більшим за 9, і я відтепер буду називати переповнені біни.

Цей термін можна обчислити шляхом повторного використання h наступним чином. Ми підраховуємо кількість способів поділу n - 10, а потім вибираємо одну з бункерів, щоб помістити 10, в результаті чого кількість розділів має один перекинутий бін. Результатом є наступна попередня функція.

g (n, d) = \ binom {n + d-1} {d-1} - \ binom {d} {1} \ біном {n + d-1-10} {d-1}

Ми продовжуємо цей шлях на n менших або рівних 29, підраховуючи всі способи розділення n - 20, потім вибираючи 2 бункери, куди ми помістимо 10-х, тим самим підраховуючи кількість розділів, що містять 2 перекинуті бункери.

Але в цей момент ми повинні бути обережними, тому що ми вже порахували розділи, що мали 2 перекинуті бункери в попередньому терміні. Мало того, але насправді ми їх перераховували двічі. Давайте скористаємося прикладом та подивимось на розділ (10,0,11) із сумою 21. У попередньому терміні ми віднімали 10, обчислювали всі розділи з решти 11 і ставили 10 в один із 3 бін. Але цього конкретного розділу можна досягти двома способами:

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

Оскільки ми також підраховували ці розділи один раз у першому терміні, загальна кількість перегородок з 2 перекритими бункерами становить 1 - 2 = -1, тому нам потрібно порахувати їх ще раз, додавши наступний термін.

g (n, d) = \ біном {n + d-1} {d-1} - \ біном {d} {1} \ біном {n + d-1-10} {d-1} + \ біном { d} {2} \ біном {n + d-1-20} {d-1}

Розмірковуючи про це трохи більше, ми незабаром виявляємо, що кількість розбитків розділу з певною кількістю перекритих бункерів підраховується в конкретному терміні, можна виразити наступною таблицею (стовпець i являє собою термін i, розділи j, розділ j з j перевитратою бункери).

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

Так, це трикутник Паскаля. Єдиний підрахунок, який нас цікавить, - це перший рядок / стовпець, тобто кількість розділів з нульовим перекриттям. А оскільки чергування суми кожного ряду, але першого дорівнює 0 (наприклад, 1 - 4 + 6 - 4 + 1 = 0), то ми їх позбавляємося і доходимо до передостанньої формули.

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

Ця функція підраховує всі числа з d цифр, що мають цифру суми n.

А тепер, що з числами з цифрою-сумою менше n? Ми можемо використовувати стандартний рецидив для біноміалів плюс індуктивний аргумент, щоб показати це

\ bar {h} (n, d) = \ біном {n + d} {d} = \ біном {n + d-1} {d-1} + \ біном {n + d-1} {d} = h (n, d) + \ bar {h} (n-1, d)

підраховує кількість розділів із знаковою сумою не більше n. І з цього f можна отримати, використовуючи ті ж аргументи, що і для g.

Використовуючи цю формулу, ми можемо, наприклад, знайти кількість важких чисел в інтервалі від 8000 до 8999 як 1000 - f(3, 20) , оскільки в цьому інтервалі є тисячі чисел, і ми повинні відняти кількість чисел із цифрою, меншою або дорівнює 28 при цьому беручи до уваги, що перша цифра вже вносить 8 до цифрної суми.

Як більш складний приклад розглянемо кількість важких чисел в інтервалі 1234..5678. Спочатку ми можемо перейти від 1234 до 1240 з кроком 1. Потім переходимо від 1240 до 1300 з кроком 10. Вищенаведена формула дає нам кількість важких чисел у кожному такому інтервалі:

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))

Тепер ми переходимо від 1300 до 2000 з кроком 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))

Від 2000 до 5000 з кроком 1000:

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

Тепер нам доведеться знову зменшити розмір кроків, переходячи від 5000 до 5600 з кроком 100, з 5600 до 5670 в кроках 10 і, нарешті, з 5670 до 5678 в кроках 1.

Приклад реалізації Python (який тим часом отримав невеликі оптимізації та тестування):

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

Редагування : Замінено код оптимізованою версією (яка виглядає ще більш потворною, ніж оригінальний код). Також виправили кілька кутових справ, поки я був на ньому. heavy(1234, 100000000)займає близько моєї мілісекунди на моїй машині.


Привіт, це рішення працює і це було правильне обчислення, проте обмеження часу для невеликих чисел становило лише 0,10 секунди, а обмеження часу для великого числа - 0,35 секунди. Наведений вище код зайняв приблизно 1 секунду. Як ви вважаєте, чи є кращий спосіб та розумний спосіб вирішити це, таким чином, пропустити деякі цифри, тому що ми вже знаємо, що конкретна кількість матиме цифру, меншу за 7? А може, якщо є розумніший спосіб вирішити це? Для вашої інформації це питання також було позначене як важке запитання.

1
@Bob: Код написаний на Python і зовсім не оптимізований. Якщо ви хочете, щоб це було швидко, напишіть це на C. Але також у чистому Python є багато можливостей для вдосконалення. Перше, що потребує оптимізації - це binomial()функція. Є ще кілька речей, які легко вдосконалити. Я опублікую оновлення через кілька хвилин.
Свен Марнах

Або ми можемо просто використовувати таблицю пошуку з попередньо обчисленими f (m, n). Враховуючи, що 200 000 000 - це межа, використання пам'яті має бути мінімальним. (У вас уже є моя +1).

@Moron: Звичайно, це найкращий варіант - я спробую.
Свен Марнах

@Moron: Мені потрібно включити таблицю пошуку у вихідний код. Зазвичай f(d, n)не викликається двічі з однаковими параметрами під час одного запуску програми.
Свен Марнах

5

Повторіть та використовуйте перестановки.

Припустимо, ми визначимо загальну функцію, яка знаходить значення між a і b з важкістю більше x:

heavy_decimal_count(a,b,x)

У вашому прикладі від a = 8675 до b = 8689 перша цифра дорівнює 8, тому киньте її - відповідь буде такою ж, як 675 до 689, і знову від 75 до 89.

Середня вага перших двох цифр 86 - 7, тому для решти цифр для середньої ваги потрібна середня вага більше 7. Таким чином, дзвінок

heavy_decimal_count(8675,8689,7)

еквівалентно

heavy_decimal_count(75,89,7)

Таким чином, наш діапазон для (нової) першої цифри становить 7 - 8 з такими можливостями:

7: 5-9
8: 0-9

Для 7 ми все ще потребуємо в середньому більше 7, що може виходити лише з кінцевої цифри 8 або 9, даючи нам 2 можливих значення.

Для 8 нам потрібно в середньому більше 6, що може виходити лише з кінцевої цифри 7-9, даючи нам 3 можливих значення.

Отже, 2 + 3 дає 5 можливих значень.

Те, що відбувається, полягає в тому, що алгоритм починається з 4-значного числа і ділить його на менші проблеми. Функція неодноразово викликала би себе простішими версіями проблеми, поки у неї є щось, з чим вона може впоратися.


2
Отже, ви претендуєте на важкий (886,887) = важкий (6,7)?

@Moron: Ні, тому що перші два 8-х змінюють поріг важкості. У прикладі перших двох було 86, що в середньому становить 7 і, отже, не змінюють поріг. Якщо (8 + 8 + x) / 3> 7, то x> 5. Так важкий (886,887,7,0) == Важкий (6,7,5,0).

@Phil H, я не думаю, що така ідея спрацює: якщо ви візьмете 9900 і 9999, це змінило б це на важкі між 0 і 99, враховуючи, наприклад, 8, а 9908 - не велике число ( @Aryabhatta).
Ганс Роггман

3

Можливо, ви можете пропустити багато кандидатів в проміжку від a до b, накопичивши їх "важкість".

якщо ви знаєте довжину вашого номера, ви знаєте, що кожна цифра може змінити важкість лише на 1 / довжину.

Отже, якщо ви починаєте з одного неважкого числа, ви повинні мати можливість обчислити наступне число, яке буде важким, якщо ви збільшите їх на одиницю.

У вашому прикладі вище, починаючи з 8680 avg = 5,5, що знаходиться на відстані 7-5,5 = 1,5 бала від вашої межі важкості, ви знаєте, що між ними є 1,5 / (1/4) = 6 чисел, які НЕ важкі.

Це слід до хитрості!


Те саме стосується ряду "важких" цифр. Ви можете просто порахувати кількість і пропустити їх!

1
Просто помножте все на кількість цифр, і ви позбудетеся від цих прискікливих /lengthс.

1

Як щодо простої рекурсивної функції? Щоб все було просто, він обчислює всі великі числа digitsцифрами та мінімальною сумою цифр 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'.

Реалізовано це в python, і він знайшов усі 9-значні великі числа за ~ 2 секунди. Трохи динамічного програмування це могло б покращити.


0

Це одне можливе рішення.

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
Ласкаво просимо до Code Golf. Коли вже відповіді на запитання, можна отримати більше відповідей, якщо вони кращі за нього в одному з критеріїв виграшу, або вони показують новий і цікавий спосіб відповісти на нього. Я не бачу, як відповідає і ваша відповідь.
ugoren

0

C, для інтервалу [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;}

// вправа

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

//результати

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

Що означає "Стандартні лазівки"?
RosLuP

1
@Riker Тут тег не <codegolf> це <швидкий алгоритм>
RosLuP
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.