Algoritma yang efisien untuk masalah visibilitas vertikal

18

Saat memikirkan satu masalah, saya menyadari bahwa saya perlu membuat algoritma yang efisien untuk menyelesaikan tugas berikut:

Masalahnya: kita diberi kotak persegi dua dimensi dari sisi n yang sisi-sisinya sejajar dengan sumbu. Kita bisa melihatnya dari atas. Namun, ada juga m segmen horisontal. Setiap segmen memiliki bilangan bulat y koordinat ( 0yn ) dan x koordinat ( 0x1<x2n ) dan menghubungkan titik (x1,y) dan (x2,y) (lihat gambar di bawah).

Kami ingin tahu, untuk setiap segmen unit di bagian atas kotak, seberapa dalam kita dapat melihat secara vertikal di dalam kotak jika kita melihat melalui segmen ini.

x{0,...,n-1}makssaya: [x,x+1][x1,saya,x2,saya]ysaya

Contoh: diberikan dan segmen yang terletak seperti pada gambar di bawah ini, hasilnya adalah (5, 5, 5, 3, 8, 3, 7, 8, 7) . Lihatlah seberapa dalam cahaya bisa masuk ke dalam kotak.m = 7 ( 5 , 5 , 5 , 3 , 8 , 3 , 7 , 8 , 7 )n=9m=7(5,5,5,3,8,3,7,8,7)

Tujuh segmen;  bagian yang diarsir menunjukkan wilayah yang dapat dijangkau oleh cahaya

Untungnya bagi kita, baik n dan m adalah cukup kecil dan kita dapat melakukan perhitungan off-line.

Algoritma termudah yang menyelesaikan masalah ini adalah brute-force: untuk setiap segmen melintasi seluruh array dan memperbaruinya jika perlu. Namun, itu memberi kita tidak terlalu mengesankan .HAI(mn)

Peningkatan yang bagus adalah dengan menggunakan pohon segmen yang mampu memaksimalkan nilai pada segmen selama kueri dan membaca nilai akhir. Saya tidak akan menjelaskan lebih lanjut, tetapi kita melihat bahwa kompleksitas waktu adalah .HAI((m+n)catatann)

Namun, saya menghasilkan algoritma yang lebih cepat:

Garis besar:

  1. Urutkan segmen dalam urutan menurun koordinat (waktu linier menggunakan variasi jenis penghitungan). Sekarang perhatikan bahwa jika setiap segmen -unit telah dicakup oleh segmen mana pun sebelumnya, tidak ada segmen berikut yang dapat mengikat berkas cahaya melalui segmen -unit ini lagi. Kemudian kita akan melakukan sapuan garis dari atas ke bawah kotak.x xyxx

  2. Sekarang mari kita perkenalkan beberapa definisi: segmen -unit adalah segmen horizontal imajiner pada sapuan yang koordinatnya bilangan bulat dan panjangnya adalah 1. Setiap segmen selama proses sapuan mungkin tidak ditandai (yaitu, berkas cahaya yang berasal dari bagian atas kotak dapat mencapai segmen ini) atau ditandai (huruf besar). Pertimbangkan segmen -unit dengan , selalu tanpa tanda. Mari kita juga memperkenalkan set . Setiap set akan berisi seluruh urutan segmen -unit bertanda yang ditandai (jika ada) dengan tanda berikut yang tidak ditandaix 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 segmen.

  3. Kami membutuhkan struktur data yang dapat beroperasi pada segmen ini dan menetapkan secara efisien. Kami akan menggunakan struktur find-union yang diperluas oleh bidang yang memegang indeks segmen -unit maksimum (indeks segmen yang tidak ditandai ).x

  4. Sekarang kita dapat menangani segmen dengan efisien. Katakanlah kita sekarang mempertimbangkan segmen -th agar (menyebutnya "query"), yang dimulai di dan berakhir di . Kita perlu menemukan semua segmen -unit tak bertanda yang terdapat di dalam segmen ke- (ini persis segmen di mana berkas cahaya akan berakhir dengan caranya). Kami akan melakukan yang berikut: pertama, kami menemukan segmen tanpa tanda pertama di dalam kueri ( Temukan perwakilan dari set yang berisi dan dapatkan indeks maksimum dari set ini, yang merupakan segmen tanpa tanda menurut definisi ). Maka indeks inix 1 x 2 x i x 1 x y x x + 1 x x 2sayax1x2 xsayax1xada di dalam kueri, tambahkan ke hasil (hasil untuk segmen ini adalah ) dan tandai indeks ini ( Kumpulan Union berisi dan ). Kemudian ulangi prosedur ini sampai kami menemukan semua segmen yang tidak ditandai , yaitu, selanjutnya Cari kueri memberi kami indeks .yxx+1xx2

Perhatikan bahwa setiap operasi pencarian-serikat kerja akan dilakukan hanya dalam dua kasus: apakah kita mulai mempertimbangkan segmen (yang dapat terjadi kali) atau kami baru saja menandai segmen unit (ini bisa terjadi kali). Dengan demikian kompleksitas keseluruhan adalah ( adalah fungsi Ackermann terbalik ). Jika ada sesuatu yang tidak jelas, saya bisa menguraikan lebih lanjut tentang ini. Mungkin saya akan dapat menambahkan beberapa gambar jika saya punya waktu.x n O ( ( n + m ) α ( n ) ) αmxnHAI((n+m)α(n))α

Sekarang saya mencapai "tembok". Saya tidak dapat menghasilkan algoritma linier, meskipun sepertinya harus ada satu. Jadi, saya punya dua pertanyaan:

  • Apakah ada algoritma linear-waktu (yaitu, ) memecahkan masalah visibilitas segmen horizontal?HAI(n+m)
  • Jika tidak, apa buktinya bahwa masalah visibilitas adalah ?ω(n+m)
mnbvmar
sumber
Seberapa cepat Anda mengurutkan segmen m Anda ?
babou
@ Bobou, pertanyaannya menentukan jenis penghitungan, yang seperti pertanyaannya, berjalan dalam waktu linier ("waktu linier menggunakan variasi jenis penghitungan").
DW
Apakah Anda mencoba menyapu dari kiri ke kanan? Yang Anda butuhkan adalah mengurutkan pada dan x 2 baik dalam langkah O ( m ) dan O ( m ) untuk berjalan ke kanan. Jadi total O ( m ) . x1x2O(m)O(m)O(m)
invalid_id
@invalid_id Ya, saya sudah mencoba. Namun, dalam hal ini garis sapuan harus bereaksi dengan tepat ketika memenuhi awal segmen (dengan kata lain, tambahkan angka yang sama dengan koordinat segmen ke multiset), memenuhi akhir segmen (menghapus kejadian y -coordinate) dan output segmen aktif tertinggi (output nilai maksimum dalam multiset). Saya belum pernah mendengar adanya struktur data yang memungkinkan kami melakukan ini dalam waktu (diamortisasi) konstan. yy
mnbvmar
@ mnbvmar mungkin saran bodoh, tapi bagaimana dengan array ukuran , Anda menyapu dan menghentikan setiap sel O ( n ) . Untuk sel evry Anda tahu maks y dan Anda bisa memasukkannya dalam matriks, selanjutnya Anda dapat melacak maksimum keseluruhan dengan variabel. nHAI(n)y
invalid_id

Jawaban:

1
  1. Pertama semacam baik dan x 2 koordinat garis di dua array terpisah A dan B . O ( m )x1x2ABO(m)
  2. Kami juga mempertahankan ukuran array bit tambahan untuk melacak segmen aktif.n
  3. Mulai menyapu dari kiri ke kanan:
  4. untuk (i=0,i<n,i++)
  5. {
  6. ..Jika dengan y nilai c O ( 1 )x1=iyc O(1)
  7. .. {
  8. .... temukan ( )max
  9. .... toko ( ) O ( 1 )maxO(1)
  10. ..}
  11. ..Jika dengan y nilai c O ( 1 )x2=iyc O(1)
  12. .. {
  13. .... temukan ( )max
  14. .... toko ( ) O ( 1 )maxO(1)
  15. ..}
  16. }

find ( ) dapat diimplementasikan menggunakan array bit dengan n bit. Sekarang setiap kali kita menghapus atau menambahkan elemen ke L kita dapat memperbarui integer ini dengan menetapkan bit masing-masing menjadi benar atau salah. Sekarang Anda memiliki dua opsi tergantung pada bahasa pemrograman yang digunakan dan asumsi n relatif kecil yaitu lebih kecil dari l o n g l o n g i n t yang setidaknya 64 bit atau jumlah tetap dari bilangan bulat ini:maxnLnlonglongint

  • Dapatkan bit paling signifikan dalam waktu konstan didukung oleh beberapa perangkat keras dan gcc.
  • Dengan mengonversi ke integer O ( 1 ) Anda akan mendapatkan nilai maksimum (tidak secara langsung tetapi Anda dapat menurunkannya).LO(1)

Saya tahu ini adalah hack karena mengasumsikan nilai maksimum untuk dan karenanya n dapat dilihat sebagai konstanta kemudian ...nn

invalid_id
sumber
Seperti yang saya lihat, dengan asumsi Anda sudah mendapatkan prosesor x86 64-bit, Anda hanya dapat menangani . Bagaimana jika n ada dalam urutan jutaan? n64n
mnbvmar
Maka Anda akan membutuhkan lebih banyak bilangan bulat. Dengan dua bilangan bulat, Anda dapat menangani hingga 128, dll. Jadi langkah penemuan maksimum O ( m ) tersembunyi dalam jumlah bilangan bulat yang diperlukan, yang mungkin masih Anda optimalkan jika m kecil. Anda menyebutkan dalam pertanyaan Anda bahwa n relatif kecil jadi saya kira itu tidak dalam urutan jutaan. Ngomong-ngomong int panjang panjang selalu setidaknya 64 bit menurut definisi bahkan pada prosesor 32-bit. nO(m)mn
invalid_id
Tentu saja itu benar, standar C ++ mendefinisikan long long intsebagai setidaknya tipe integer 64-bit. Namun, bukankah jika besar dan kita menyatakan ukuran kata sebagai w (biasanya w = 64 ), maka masing - masing akan mengambil O ( nnww=64findwaktu? Maka kita akan berakhir dengan totalO(mnO(nw). O(mnw)
mnbvmar
Ya, sayangnya untuk nilai besar dari itulah masalahnya. Jadi sekarang saya bertanya-tanya seberapa besar n akan dalam kasus Anda dan apakah itu dibatasi. Jika memang dalam urutan jutaan hack-around ini tidak akan berfungsi lagi, tetapi jika c w n untuk nilai c rendah itu akan cepat dan praktis O ( n + m ) . Jadi pilihan algoritma terbaik, seperti biasa, bergantung pada input. Misalnya untuk jenis penyisipan n 100 biasanya lebih cepat daripada menggabungkan jenis, bahkan dengan waktu berjalan O ( n 2 ) dibandingkan dengannncwncO(n+m)n100O(n2) . O(nlogn)
invalid_id
3
Saya bingung dengan pilihan format. Anda tahu Anda bisa mengeset kode di sini, kan?
Raphael
0

Saya tidak memiliki algoritma linier, tetapi yang ini tampaknya O (m log m).

Urutkan segmen berdasarkan koordinat pertama dan tinggi. Ini berarti bahwa (x1, l1) selalu datang sebelumnya (x2, l2) setiap kali x1 <x2. Juga, (x1, l1) pada ketinggian y1 datang sebelum (x1, l2) pada ketinggian y2 setiap kali y1> y2.

Untuk setiap subset dengan koordinat pertama yang sama, kami melakukan hal berikut. Biarkan segmen pertama menjadi (x1, L). Untuk semua segmen lain dalam subset: Jika segmen lebih panjang dari yang pertama, kemudian ubah dari (x1, xt) menjadi (L, xt) dan tambahkan ke subset L-dalam urutan yang tepat. Kalau tidak jatuhkan. Akhirnya, jika subset berikutnya memiliki koordinat pertama kurang dari L, maka bagi (x1, L) menjadi (x1, x2) dan (x2, L). Tambahkan (x2, L) ke subset berikutnya dalam urutan yang benar. Kita bisa melakukan ini karena segmen pertama dalam subset lebih tinggi dan mencakup rentang dari (x1, L). Segmen baru ini mungkin yang mencakup (L, x2), tetapi kita tidak akan tahu itu sampai kita melihat subset yang memiliki koordinat pertama L.

Setelah kami menjalankan semua subset, kami akan memiliki seperangkat segmen yang tidak tumpang tindih. Untuk menentukan nilai Y untuk X yang diberikan, kita hanya perlu menjalankan segmen yang tersisa.

Jadi apa kerumitannya di sini: Jenisnya adalah O (m log m). Looping melalui subset adalah O (m). Pencarian juga O (m).

Jadi sepertinya algoritma ini tidak bergantung pada n.

Tidak Ada Yang Tertentu
sumber