Ефективні алгоритми для проблеми вертикальної видимості


18

Під час роздумів над однією проблемою я зрозумів, що мені потрібно створити ефективний алгоритм для вирішення наступного завдання:

Проблема: нам дається двовимірне квадратне поле зі сторони , сторони якого паралельні осям. Ми можемо заглянути в нього через верх. Однак є і горизонтальних сегментів. Кожен сегмент має ціле число -координат ( ) і -координати ( ) і з'єднує точки та (дивіться на малюнок нижче).m y 0 y n x 0 x 1 < x 2n ( x 1 , y ) ( x 2 , y )nmy0ynx0x1<x2n(x1,y)(x2,y)

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

Формально для ми хочемо знайти .max i : [ x , x + 1 ] [ x 1 , i , x 2 , i ] y ix{0,,n1}maxi: [x,x+1][x1,i,x2,i]yi

Приклад: задані n=9 і m=7 сегментів, розташованих як на малюнку нижче, результат (5,5,5,3,8,3,7,8,7) . Подивіться, як глибоке світло може потрапити в коробку.

Сім сегментів;  затінена частина вказує область, до якої можна дістатися світлом

До щастя для нас, як n і m є досить малі , і ми можемо зробити обчислення офф-лайн.

Найпростіший алгоритм вирішення цієї проблеми - груба сила: для кожного сегмента пройдіть весь масив і оновіть його, де необхідно. Однак це дає нам не дуже вражаючий O(mn) .

Велике вдосконалення полягає у використанні сегментного дерева, яке здатне максимізувати значення на сегменті під час запиту та читати кінцеві значення. Я не опишу його далі, але ми бачимо, що часова складність становить .O((m+n)logn)

Однак я придумав більш швидкий алгоритм:

Контур:

  1. Сортуйте відрізки у порядку зменшення -координати (лінійний час, використовуючи варіацію сортування підрахунку). Тепер зауважте, що якщо будь- який сегмент -одиниці раніше був охоплений будь-яким сегментом, жоден наступний сегмент вже не може зв'язати промінь світла, що проходить через цей сегмент -одиниці. Тоді зробимо підмітання лінії зверху до нижньої частини коробки.x xyxx

  2. Тепер введемо кілька визначень: -одиничний сегмент - це уявний горизонтальний відрізок на розгортці, -координати якого є цілими числами, а довжина - 1. Кожен сегмент під час процесу розгортання може бути або немаркованим (тобто, промінь світла, що йде від У верхній частині поля може бути досягнутий цей відрізок) або позначений (навпроти випадку). Розглянемо відрізок -одиниці з , завжди без позначення. Введемо також множини . Кожен набір буде містити цілу послідовність послідовно позначених сегментів -unit (якщо такі є) із наступним немаркованим позначеннямx x x 1 = n x 2 = n + 1 S 0 = { 0 } , S 1 = { 1 } , ... , S n = { n } xxxxx1=nx2=n+1S0={0},S1={1},,Sn={n} x сегмент.

  3. Нам потрібна структура даних, яка здатна працювати на цих сегментах та встановлювати ефективно. Ми будемо використовувати структуру find-union, розширену полем, що містить максимальний індекс сегмента -unit (індекс немаркованого сегмента).x

  4. Тепер ми можемо ефективно обробляти сегменти. Скажімо, зараз ми розглядаємо порядок -го сегмента (називаємо його "запит"), який починається з і закінчується в . Нам потрібно знайти всі немарковані сегменти -одиниці, які містяться всередині -го сегмента (саме такі сегменти, на яких промінь світла закінчиться). Ми зробимо наступне: по-перше, ми знаходимо перший немаркований сегмент всередині запиту ( Знайдіть представника набору, в якому міститься , і отримаємо максимум індексу цього набору, який є неозначеним сегментом за визначенням ). Тоді цей показникx 1 x 2 x i x 1 x y x x + 1 x x 2ix1x2 xix1xзнаходиться всередині запиту, додайте його до результату (результат для цього сегмента ) і позначте цей індекс ( набори об'єднань, що містять і ). Потім повторіть цю процедуру, поки ми не знайдемо всі немарковані сегменти, тобто наступний пошук знайде нам індекс .yxx+1xx2

Зауважте, що кожна операція пошуку об'єднання буде виконана лише у двох випадках: або ми починаємо розглядати сегмент (що може статися разів), або ми лише позначили сегмент -unit (це може статися разів). Таким чином, загальна складність становить ( - обернена функція Акермана ). Якщо щось не зрозуміло, я можу детальніше зупинитися на цьому. Можливо, я зможу додати кілька фотографій, якщо матиму час.mxnO((n+m)α(n))α

Тепер я досяг «стіни». Я не можу придумати лінійний алгоритм, хоча, здається, він повинен бути. Отже, у мене є два питання:

  • Чи існує алгоритм лінійного часу (тобто ), що вирішує задачу про видимість горизонтального сегмента?O(n+m)
  • Якщо ні, що є доказом того, що проблема видимості - ?ω(n+m)

Як швидко ви сортуєте свої m сегменти?
babou

@babou, питання визначає сортування підрахунку, який, як йдеться в запитанні, працює в лінійний час ("лінійний час з використанням варіації підрахунку сортування").
DW

Ви спробували підмітати зліва направо? Все, що вам потрібно, - це сортування на та як у кроках O ( m ), так і щоб піти праворуч. Отже, загалом . x1x2O(m)O(m)O(m)
invalid_id

@invalid_id Так, я спробував. Однак у цьому випадку лінія розгортки повинна реагувати належним чином, коли вона відповідає початку сегмента (іншими словами, додайте число, рівне -координату відрізка до мультисети), відповідає кінці сегмента (видаліть виникнення -координація) і вивести найвищий активний сегмент (максимальне значення виводу в мультисеті). Я не чув жодної структури даних, яка дозволила б нам це робити (амортизований) постійний час. yy
mnbvmar

@mnbvmar, можливо, німа пропозиція, але як щодо масиву розміру , ви змітаєте та зупиняєте кожну клітинку O ( n ) . Для осередку evry ви знаєте max y і можете вводити його в матрицю, крім того, ви можете відстежувати загальний максимум за допомогою змінної. nO(n)y
invalid_id

Відповіді:


1
  1. Сортувати обидва і х 2 координат ліній в двох окремих масивах A і B . O ( м )x1x2ABO(m)
  2. Ми також підтримуємо допоміжний бітовий масив розміром для відстеження активних сегментів.n
  3. Почніть підмітати зліва направо:
  4. для (i=0,i<n,i++)
  5. {
  6. ..якщо з у значення гр O ( 1 )x1=iyc O(1)
  7. .. {
  8. .... знайти ( )max
  9. .... магазин ( ) O ( 1 )maxO(1)
  10. ..}
  11. ..якщо з у значення гр O ( 1 )x2=iyc O(1)
  12. .. {
  13. .... знайти ( )max
  14. .... магазин ( ) O ( 1 )maxO(1)
  15. ..}
  16. }

find ( ) може бути реалізований за допомогою бітового масиву з n бітами. Тепер, коли ми видаляємо або додаємо елемент до L, ми можемо оновити це ціле число, встановивши відповідно біт на true або false. Тепер у вас є два варіанти в залежності від використовуваної мови програмування, і припущення n порівняно невелике, тобто менше, ніж l o n g l o n g i n t, що становить принаймні 64 біт або фіксовану кількість цих цілих чисел:maxnLnlonglongint

  • Отримання найменш значущого біта за постійний час підтримується деяким обладнанням та gcc.
  • Перетворивши на ціле число O ( 1 ), ви отримаєте максимум (не безпосередньо, але ви можете його отримати).LO(1)

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


Як я бачу, якщо припустити, що у вас 64-розрядний процесор x86, ви можете обробляти лише . Що робити, якщо n в порядку мільйонів? n64n
mnbvmar

Тоді вам знадобиться більше цілих чисел. З двома цілими числами ви можете обробити до 128 і т.д. Отже, максимальний крок пошуку O ( m ) прихований у кількості необхідних цілих чисел, які ви все одно можете оптимізувати, якщо m невеликий. Ви згадали у своєму питанні, що п порівняно мало, тому я здогадався, що це не в порядку мільйонів. До речі, довгий int завжди є щонайменше 64 біт за визначенням навіть на 32-бітному процесорі. nO(m)mn
invalid_id

Звичайно, це правда, стандарт C ++ визначає long long intяк принаймні 64-бітний цілочисельний тип. Однак чи не так, що якщо величезна, і розмір слова позначаємо як w (як правило, w = 64 ), то кожен буде приймати O ( nnww=64findчас? Тоді ми б закінчилися сумарнимO(mnO(nw). O(mnw)
mnbvmar

Так, на жаль, для великих значень це так. Тож зараз мені цікаво, яка велика n буде у вашому випадку та чи обмежена вона. Якщо це дійсно в порядку мільйонів, цей злом більше не працюватиме, але якщо c w n при низьких значеннях c, він буде швидким і практично O ( n + m ) . Тож найкращий вибір алгоритму - як завжди, залежить від введення. Наприклад, для n 100 сортування вставки зазвичай швидше, ніж сортування, навіть із часом роботи O ( n 2 ) порівняно зnncwncO(n+m)n100O(n2) . O(nlogn)
invalid_id

3
Мене бентежить ваш вибір форматування. Ви знаєте, що тут можна ввести код, правда?
Рафаель

0

У мене немає лінійного алгоритму, але цей здається O (m log m).

Сортуйте відрізки на основі першої координати та висоти. Це означає, що (x1, l1) завжди виникає раніше (x2, l2), коли x1 <x2. Крім того, (x1, l1) на висоті y1 передує (x1, l2) на висоті y2, коли y1> y2.

Для кожного підмножини з однаковою першою координатою робимо наступне. Нехай перший відрізок буде (x1, L). Для всіх інших сегментів підмножини: Якщо сегмент довший першого, то змініть його з (x1, xt) на (L, xt) і додайте його до L-підмножини у відповідному порядку. Інакше киньте його. Нарешті, якщо наступний підмножина має першу координату менше L, то розділіть (x1, L) на (x1, x2) і (x2, L). Додайте (x2, L) до наступного підмножини у правильному порядку. Ми можемо це зробити, тому що перший сегмент у підмножині вищий і охоплює діапазон від (x1, L). Цей новий сегмент може бути тим, що охоплює (L, x2), але ми цього не будемо знати, поки не подивимось на підмножину, яка має першу координату L.

Після проходження всіх підмножин у нас з'явиться набір сегментів, які не перетинаються. Щоб визначити, що значення Y для даного X, нам залишиться пропустити лише всі інші сегменти.

Тож у чому полягає складність: Сорт - O (m log m). Прокручування через підмножини - O (m). Пошук також є O (m).

Отже, здається, що цей алгоритм не залежить від n.

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