Hai
Baru-baru ini saya memecahkan masalah dari arsip
Timus Online Judge dan menemukan bagian yang disebut
tugas pemrograman dinamis . Jenis tugas ini sangat menarik bagi saya, karena seringkali pendekatan ini memastikan kecepatan dan keanggunan solusi. Apa itu pemrograman dinamis?
Pemrograman dinamis adalah pendekatan untuk memecahkan masalah di mana ada pembagian menjadi subtugas yang "lebih sederhana" dibandingkan dengan yang asli. Kata "dinamis" dekat artinya dengan "induktif": diasumsikan bahwa jawabannya dikenal untuk beberapa makna
, dan saya ingin menemukan jawabannya
. Dalam matematika, ini disebut transisi induktif, dan merupakan ide utama pemrograman dinamis.
Contoh sederhana
Tugas yang paling mencolok dan indikatif adalah tugas komputasi
jumlah urutan Fibonacci.
Diketahui bahwa urutan memiliki sifat-sifat berikut:
Ini segera menyiratkan rumus pengulangan:
int Fibonacci(int n) { if(n == 1 || n == 2) return 1; return Fibonacci(n-1) + Fibonacci(n-2); }
Jika rekursi mencari angka "dari akhir", maka metode berikut secara berurutan menghitung semua angka yang terletak di antara
dan
:
int dpFibonacci(int n) { int prev1 = 1; int prev2 = 1; int curr = 0; for(int j = 2; j < n; j++) { curr = prev1 + prev2; prev1 = prev2; prev2 = curr; } return curr; }
Jelas bahwa untuk yang cukup besar
algoritma ini bekerja lebih cepat: tidak menghitung nilai antara beberapa kali. Pertimbangkan contoh yang sedikit lebih rumit.
Contoh 1. Anda berjalan di tangga tol. Untuk melangkah
langkah Anda harus membayar
koin. Anda dapat melangkah ke langkah berikutnya atau melompati satu langkah. Tugas: untuk lulus
langkah dan menghabiskan koin sesedikit mungkin.
Jelas bahwa melangkah di setiap langkah, kami meminimalkan jumlah "pembayaran", tetapi kami bisa mengalami langkah yang sangat mahal, yang ingin kami hindari. Buat array nilai
di mana pada
-tempat akan menjadi (minimum) jumlah koin yang harus dikeluarkan untuk sampai ke
langkah th. Segera jelas itu
. Dan kemudian kita akan mengambil minimal dua langkah sebelumnya dan menambahkan biaya langkah itu sendiri:
Kami sedikit mengubah kondisi masalah: misalkan pada beberapa langkah Anda bisa mendapatkan koin (ini artinya
) Apa yang perlu diubah dalam algoritma sehingga memberikan hasil yang benar?
SolusiKita hanya perlu mengubah "awal" dinamika kita. Jika tangga pertama tidak membawa kita koin, maka disarankan untuk melompatinya, jika , lebih baik untuk melangkah dan mengumpulkan koin Anda. Jadi .
Pertimbangkan contoh lain yang menggunakan dinamika "dua dimensi".
Contoh 2. Di labirin ada
kamar, yang masing-masing berisi emas (dalam sangkar
kebohongan
emas). Tugasnya adalah menentukan jumlah maksimum emas yang dapat dikumpulkan dengan rute optimal dari suatu titik
to the point
jika Anda bisa turun atau ke kanan.
Jadi, kami ingin tahu rute terbaik ke sel
. Kita bisa sampai di sini dari dua sel -
dan
. Mengingat bahwa rute optimal untuk dua sel ini diketahui (mereka disimpan dalam beberapa tabel
), lalu jawaban untuk sel
diperoleh sebagai berikut:
Ini adalah tugas pemrograman dinamis klasik lainnya, modifikasi yang cukup umum dalam tugas pemrograman olahraga. Tugas serupa dijelaskan secara lebih rinci di
sini .
Tugas yang lebih menantang
Jika diinginkan, pendekatan dinamis dapat kacau di mana pun Anda inginkan. Pertimbangkan
tugas dari arsip Hakim Timus Online.
Perumusan matematika dari masalah adalah sebagai berikut: diperlukan untuk menemukan jumlah minimum syarat yang diperlukan untuk menguraikan angka yang diberikan ke dalam kotak penuh.
Seperti sebelumnya, anggaplah kita tahu jawaban untuk semua angka
yang disimpan dalam beberapa array
dan kami ingin menemukannya
.
Ambil nomor ini
dan menganalisis situasi apa yang mungkin terjadi:
- adalah kotak penuh. Dalam hal ini .
- Mungkin nomor sebelumnya adalah kotak lengkap. Lalu .
Secara umum, opsi untuk menambahkan unit ke unit sebelumnya tampaknya tidak terlalu buruk.
Kami melanjutkan sebagai berikut: kami mencari dekomposisi
sedemikian rupa
Sejak
- kotak penuh itu
, dan
yaitu, kami menemukan partisi yang lebih baik daripada
, dan jawabannya dalam kasus ini adalah
Contoh kode Java yang mengimplementasikan algoritma ini: for(int k = 1; k <= n; k++) { int best = d[k - 1] + 1;
Pertimbangkan
masalah berikut. Tujuannya adalah untuk membangun tangga dari
kubus sesuai aturan:
- tangga memiliki setidaknya dua langkah;
- tangga tidak dapat memiliki dua langkah yang identik;
- langkah-langkah tangga berjalan dalam urutan menaik (yaitu, yang berikutnya lebih besar dari yang sebelumnya).
Kali ini kita akan membangun dinamika dua dimensi. Buat tabel
di mana posisinya
jumlah tangga yang terdiri dari
kubus yang tingginya tidak melebihi
. Jika berhasil, maka jawaban untuk masalah kita adalah jumlah
Jadi, kita akan memecahkan masalah menemukan jumlah tangga yang terdiri dari
kubus yang tinggi
. Gambar menunjukkan tangga yang jatuh ke
:

Karena kita tahu semua tangga, yang terdiri dari lebih sedikit kubus, kita akan βmemisahkanβ tangga
kolom kanan. Hasilnya adalah tangga c
kubus. Contoh untuk
:

Tetapi untuk tangga seperti itu, hasilnya sudah diketahui, jadi kami akan memilah-milah semua tangga dengan siklus masuk
dan tambahkan semua hasil. Dengan cara ini
Sekarang kita akan mengurutkan ketinggian tangga:
Akhirnya berubah
dari
sebelumnya
kami mendapatkan jawabannya.
Penting : dalam proses membangun matriks kita, perlu dipertimbangkan
, karena kalau tidak beberapa jenis tangga akan "hilang" (ketika "memisahkan diri"), tetapi tidak perlu dikatakan bahwa tangga seperti itu tidak memenuhi kondisi masalah, jadi jawabannya adalah nomornya.
.
Contoh kode Java yang mengimplementasikan algoritma ini: dp = new long[n + 1][n+1]; d[1][1] = 1; d[2][1] = 0; d[2][2] = 1; for(int i = 3; i < n + 1; i++) { for(int j = 2; j <i; j++) { long cnt = 0; for(int k = 1; k < j; k++) { cnt += d[i - j][k]; } d[i][j] = cnt; } d[i][i] = 1;
Tugas selanjutnya diselesaikan menggunakan array satu dimensi.
Jadi apa yang kita miliki. Ent pertama tahu 2 kata. Setiap ent mengajarkan semua kata yang dia tahu dirinya sendiri dua sen: muda dan tua. Pada gilirannya, kaum muda diajarkan sebanyak mungkin kata yang dia tahu, dan yang lama hanya diajarkan satu kata. Anda perlu tahu berapa banyak yang tahu persis
kata-kata (perlu untuk menyimpulkan jumlah modul ini
)
Solusinya cukup sederhana. Buat sebuah array
di mana pada
-tempat kita akan menyimpan jumlah en (modulo
) siapa tahu
kata-kata. Itu semua dimulai dengan ent pertama, yang tahu dua kata, oleh karena itu
. Dan kemudian semuanya sederhana:
- Semua kata yang tahu jumlah kata ganjil sudah tua dan hanya bisa dipelajari dari kata-kata sebelumnya. Karena itu aneh
- Adapun es yang tahu jumlah kata yang genap, ini adalah mereka yang menerima jumlah kata yang sama dari elf (muda) mereka yang telah belajar dari yang sebelumnya (lama); bahkan untuk kami punya .
Masih berurusan dengan modulo perhitungan. Agar tidak menyimpan angka besar, kami akan segera mengingat semua nilai modulo.
Contoh kode Java yang mengimplementasikan algoritma ini: int[] d = new int[K + 1]; if(K >= 2) d[2] = 1; if(P != 1) { for(int i = 3; i <= K; i++) { if(i % 2 != 0) { d[i] = d[i - 1]; } else { d[i] = ((d[i/2] % P) + d[i - 1] % P) % P; } } } else d[K] = 0;
Sumber daya yang digunakan:
- Hakim Online Timus;
- Sedikit tentang pemrograman dinamis;
- Modulo properti perbandingan.