Ефективне обчислення найменшого цілого числа з n дільниками


9

Щоб вирішити цю проблему, я спочатку зауважив це

ϕ(p1e1 p2e2 pkek)=(e1+1)(e2+1)(ek+1)

Де - кількість (не обов'язково простих) дільників . Якщо - найменше ціле число, таке що , тоϕ(m)mmϕ(m)=n

ϕ(m)=n
(e1+1)(e2+1)(ek+1)=n

Тепер ми повинні вибрати таким, щоб був мінімальним. Вибір для тривіальний - вони просто букви в порядку зростання.eiipieip

Однак моя перша думка про вибір була неправильною. Я думав, що ви можете просто помножити , сортувати коефіцієнти у порядку зменшення та відняти 1. Більшість випадків це працює нормально, наприклад, найменше ціле число з дільниками:einn=15

15=53
15=(4+1)(2+1)
m=2432=144

Але для це неправильно :n=16

16=2222
16=(1+1)(1+1)(1+1)(1+1)
m=21315171=210

Тоді як правильна відповідь:

16=(3+1)(1+1)(1+1)
m=233151=120

Тож зрозуміло, що іноді нам потрібно об'єднати фактори. У цьому випадку тому, що . Але я точно не бачу чистої та прямої стратегії злиття. Наприклад, можна подумати, що ми завжди повинні зливатися у сили, але це неправда:71>222

1552=(96+1)(1+1)(1+1)(1+1)(1+1)
m=296315171111>296335171

Я не можу одразу придумати приклад, але мій інстинкт говорить, що деякі жадібні підходи можуть зазнати невдачі, якщо вони спочатку з’єднають неправильні сили.

Чи існує проста оптимальна стратегія об'єднання цих повноважень, щоб отримати правильну відповідь?


Додаток Жадібний алгоритм, який перевіряє кожне можливе злиття та виконує найкраще на основі об'єднання за об'єднанням, не працює на . Серія злиття один за одним:n=3072

22315171111131171191231291311

23325171111131171191231291

25335171111131171191231

Однак оптимальним рішенням є:

27335271111131171191

@orlp: Моя пропозиція була: виправити (скажімо, ) та виправити (скажімо, ). Тоді ви намагаєтеся мінімізувати , за умови . Таким чином, працюючи з фіксованим числом (простих ліній), ви можете ігнорувати ускладнення того, чи повинен певний простір відображатися в глобальному мінімумі чи ні. Ви знаходите мінімум для кожного , а потім берете хв. n24m2k1log(2)+k2log(3)k1k2=24mm
Стів D

Відповіді:


1

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

Ідея полягає в тому, щоб розглянути , яке ми визначаємо як "найменше додатне ціле число з точно дільниками і різними простими множниками". Ми робимо прості спостереження:T(n,m)nm

T(n,1)=2n1T(2m,m)=p1p2pm

І у нас також є рецидив:

T(n,m)=mind|n[T(nd,m1)pmd1]

Нарешті, кількість, яку ви шукаєте,

min1ilog(n)T(n,i)

З цією метою, ось код Python, який погоджується з усіма числами, які ви вказали вище. Зауважте, що він працює з логарифмами, щоб зменшити числа: тому фактичне ціле число ви шукаєте round(2**smallest(n)).

import functools
import itertools
import math

# All primes less than 100.
PRIMES = [
  2, 3, 5, 7, 11,
  13, 17, 19, 23, 29,
  31, 37, 41, 43, 47,
  53, 59, 61, 67, 71,
  73, 79, 83, 89, 97,
]

LOG_PRIMES = [math.log2(p) for p in PRIMES]

def smallest(n):
  max_factors = math.ceil(math.log2(n))
  min_so_far = float('Infinity')
  factors = factorize(n)
  memo = {}
  for i in range(1, max_factors+1):
    t = T(n,i, factors, memo)
    if 0.0 < t < min_so_far:
      min_so_far = t
  return min_so_far

def T(n, m, factors=None, memo=None):
  if memo is None:
    memo = {}
  if n < 2 or m < 1:
    return 0
  elif m == 1:
    # Everything on the smallest prime.
    return (n-1) * LOG_PRIMES[0]
  elif n < 2**m:
    return 0
  elif n == 2**m:
    # Product of first m primes, in log.
    return sum(LOG_PRIMES[:m])
  elif (n,m) in memo:
    return memo[(n,m)]

  if factors is None:
    factors = factorize(n)
  if len(factors) < m:
    return 0

  smallest = float('Infinity')  
  for factor_list in powerset(factors):
    divisor = product(factor_list)
    first = T(divisor, m-1, factor_list, memo)
    # No such product.
    if first < 1.0:
      continue
    second = (n/divisor - 1) * LOG_PRIMES[m-1]
    total = first + second
    if total < smallest:
      smallest = total

  memo[(n,m)] = smallest
  return smallest

def product(nums):
  return functools.reduce(lambda x,y: x*y, nums, 1)

def factorize(n):
  prime_factors = []
  for p in PRIMES:
    while n%p == 0:
      n //= p
      prime_factors.append(p)
    if n == 1:
      break
  return prime_factors

def powerset(lst):
  # No empty set.
  return itertools.chain.from_iterable(itertools.combinations(lst, r) 
                                       for r in range(1, len(lst)+1))

Зауваження, які ви посилаєтесь, здаються видаленими, на жаль, але це, безумовно, оптимально (в сенсі обчислення найменшого можливого цілого числа з точно коефіцієнтами). Це оптимальність часової складності, в якій ви не впевнені? Я не знаю чітку межу для кількості дільників цілого , але навіть при дуже песимістичній межі ваш алгоритм є лише , який повинен бути досить швидким для у десятках тисяч! (BTW: Я писав той самий алгоритм (мінус деякі оптимізації), але ви там спочатку потрапили, молодець!)nnO(n)O(n2logn)n
j_random_hacker

@j_random_hacker: Так, не впевнений, що сталося з цими коментарями: їх було багато, і вони зараз усі пропали! Я справді говорив про складність часу; Я насправді думаю, що це, ймовірно, ближче до , але кількість дільників - хитра функція. Звичайно, наведений вище код, безумовно, можна оптимізувати краще: наприклад , не враховує дублікатів. O(nlogn)powerset
Стів D

Я вважаю, що це простіше ефективно реалізувати, використовуючи динамічне програмування: gist.github.com/orlp/0fbb7784782712bc7c411aa58a188143 Мені насправді не подобається фокус з логарифмом - обмежена точність з плаваючою точкою в якийсь момент накрутить речі. Якщо говорити, я не вірю, що це насправді швидше, ніж генерувати всі мультиплікативні розділи. Насправді я вважаю, що саме це робиться в маскуванні!
orlp

Після більш детального ознайомлення з коментарем @ orlp та вашого коду, я думаю, що важливо, щоб складність часу (і практична ефективність) змінилася for factor_list in powerset(factors)на щось, що генерує кожен окремий подільник nрівно одного разу. Таким чином, для, скажімо, , коли ви розглядаєте рішення, що містять саме перші простих простих чисел, як їх чіткі основні фактори, ви будете робити нерекурсивну роботу замість , яка є експоненціальною в . n=2k3k2kO(k2)O((2kk))k
j_random_hacker

1
@orlp: Я неправильно зрозумів термін "мультиплікативні розділи", вибачте. Дякуємо за код Python. Щоб зрозуміти, чому алгоритм Стіва Д не паралельний цьому коду, розглянемо multiplicative_partitions(24), який виробляє (серед інших) розділи [4, 3, 2]і [6, 2, 2]які (після зміни порядку для того, щоб надати найменший основний коефіцієнт найвищому показнику) відповідають рішенням і відповідно. Алгоритм Стіва Д. ніколи не буде враховувати останнє рішення, оскільки він вже визначив, що дозвіл . 2332512531512332=72<2531=96
j_random_hacker

-1

Можливими кандидатами на "найменше ціле число з n дільниками" є цілі числа форми де a ≥ b ≥ c ... і (a + 1) (b + 1) (c + 1) ... = n.2a·3b·5c...

Тому вам потрібно знайти всі способи виразити n як добуток цілих чисел ≥ 2 у незростаючому порядку та обчислити та перевірити відповідні кандидати. Наприклад, коли n = 16, 16 = 8 · 2 = 4 · 4 = 4 · 2 · 2 = 2 · 2 · 2 · 2, то можливості є , , , , а найменший - .27·323·3323·3·52·3·5·723·3·5=120

Якщо n добуток двох простих чисел p · q, p ≥ q, єдиними кандидатами є і , а останній завжди менший .2pq12p1·3q1

Ви можете з'ясувати деяку умову, коли може бути фактор наприклад, перевіривши, чи для деяких prime x, що не є фактором. У прикладі n = 16 є коефіцієнт тому що .2ab12ab1>2a1·xb12323<2·7


3
Пробачте, але це зовсім не відповідає на моє запитання, воно просто підсумовує те, що я знайшов у своєму питанні. Назва саме така: назва, а не саме питання. Я відчуваю, що ви лише прочитали заголовок, перш ніж відповісти. Справжнє запитання знаходиться внизу тексту мого питання.
orlp

На це відповідь в останньому абзаці.
gnasher729

@ gnasher729 Це далеко не відповідь на питання "ефективно обчислити", або навіть для "оптимальної стратегії об'єднання"
yo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.