Anda di halaman 1dari 528

Perkenalan Pemrograman Kompetitif Dasar

Tim Olimpiade Komputer Indonesia

1/22
Perkenalan

Selamat datang di topik Pemrograman Kompetitif Dasar!

Anda diharapkan telah menguasai materi pemrograman dasar, dan:

• Mengetahui setidaknya satu bahasa pemrograman.


• Mampu membaca dan menulis program.

Mulai dari bab ini, seluruh kode program akan dituliskan dalam
pseudocode.

2/22
Pseudocode

• Merupakan bahasa informal yang mirip dengan bahasa


pemrograman untuk mendeskripsikan program.
• Biasa digunakan pada materi pembelajaran algoritma.
• Pseudocode sendiri bukanlah bahasa pemrograman
sungguhan.

3/22
Contoh Pseudocode

insertionSort(A)
1 for i = 2 to A.length
2 j =i
3 while (j > 1) and (A[j] < A[j − 1])
4 swap(A[j], A[j − 1])
5 j = j −1

4/22
Pseudocode (lanj.)

• Memahami pseudocode pada awalnya mungkin sulit.


• Seiring berjalannya waktu, Anda akan terbiasa memahami dan
menggunakan pseudocode dengan mudah.

5/22
Tentang Pemrograman Kompetitif

Pemrograman Kompetitif
Adalah penyelesaian soal yang terdefinisi dengan jelas dengan
menulis program komputer dalam batasan-batasan tertentu
(memori dan waktu).

6/22
Tentang Pemrograman Kompetitif (lanj.)

Peserta ditantang untuk:


• Menganalisis permasalahan
• Merancang algoritma solusi
• Menuliskannya dalam bentuk program

7/22
Tentang Pemrograman Kompetitif (lanj.)

• Masalah yang diberikan selalu terdefinisi dengan jelas


(well-defined).
• Contohnya, batasan masalah dan asumsi yang diperlukan
diberikan dengan akurat dan tidak ambigu.
• Solusi ditulis dalam bentuk program program dan memenuhi
batas-batas yang ditentukan.
• Batas yang ditentukan: waktu, memori, dan lainnya.

8/22
Contoh Soal Pemrograman Kompetitif

• Terdapat N ruangan yang dinomori dari 1 hingga N.


• Ruangan ke-i memiliki sebuah lampu dan sebuah tombol.
• Bila tombol itu ditekan, keadaan lampu pada seluruh ruangan
ke-x akan berubah (dari mati menjadi menyala, atau
sebaliknya), yang mana x habis dibagi i.
• Contoh, bila N = 10 dan tombol di ruangan ke-2 ditekan,
maka keadaan lampu pada ruangan 2, 4, 6, 8, dan 10 akan
berubah.
• Bila pada awalnya seluruh lampu berada pada keadaan mati,
dan tombol pada setiap ruangan ditekan tepat sekali,
bagaimanakah keadaan lampu pada ruangan ke-N?

9/22
Contoh Soal Pemrograman Kompetitif (lanj.)

• Batas waktu: 1 detik.


• Batas memori: 32 MB.
• Nilai N dijamin selalu memenuhi 1 ≤ N ≤ 1014 .

10/22
Solusi Naif

Salah satu cara penyelesaiannya adalah dengan mensimulasikan


skenario pada deskripsi soal:
• Kita cukup memperhatikan lampu pada ruangan ke-N saja.
• Mulai dari ruangan ke-1, dipastikan keadaan lampu pada
ruangan ke-N akan berubah (N habis dibagi 1).
• Lanjut ke ruangan ke-2, periksa apakah 2 habis membagi N.
Bila ya, ubah keadaan lampu ke-N.
• Lanjut ke ruangan ke-3, periksa apakah 3 habis membagi N.
Bila ya, ubah keadaan lampu ke-N.
• ... dan seterusnya hingga ruangan ke-N.

11/22
Solusi Naif (lanj.)

• Setelah selesai disimulasikan, periksa keadaan lampu ruangan


ke-N dan cetak jawabannya.
• Kompleksitas solusi ini adalah O(N), dan hanya akan bekerja
untuk nilai N yang kecil.
• Untuk N yang lebih besar, misalnya N = 109 , kemungkinan
besar diperlukan waktu lebih dari 1 detik.
• Solusi ini tidak akan mendapatkan nilai penuh, atau bahkan 0,
tergantung skema penilaian yang digunakan.

12/22
Solusi yang Lebih Baik

• Dengan sedikit observasi, yang sebenarnya perlu dilakukan


adalah menghitung banyaknya pembagi dari N.
• Apabila banyaknya pembagi ganjil, berarti pada akhirnya
lampu di ruangan ke-N akan menyala (mengapa?).
• Bila genap, berarti lampu di ruangan ke-N akan mati.

13/22
Solusi yang Lebih Baik (lanj.)

• Untuk menghitung banyaknya pembagi dari N dengan efisien,


lakukan faktorisasi prima terlebih dahulu.
• Misalkan N = 12, maka faktorisasi primanya adalah 22 × 3.
• Untuk menjadi pembagi dari 12, suatu bilangan hanya boleh:
• Memiliki faktor 2 maksimal sebanyak 2.
• Memiliki faktor 3 maksimal sebanyak 1.
• Tidak boleh memiliki faktor lainnya.

14/22
Solusi yang Lebih Baik (lanj.)

Sebagai contoh, berikut daftar seluruh pembagi dari 12:


• 1 = 20 × 30
• 2 = 21 × 30
• 3 = 20 × 31
• 4 = 22 × 30
• 6 = 21 × 31
• 12 = 22 × 31

15/22
Solusi yang Lebih Baik (lanj.)

• Banyaknya pembagi dari 12 sebenarnya sama saja dengan


banyaknya kombinasi yang bisa dipilih dari {20 , 21 , 22 } dan
{30 , 31 }.
• Banyaknya kombinasi sama dengan mengalikan banyaknya
elemen pada tiap-tiap himpunan.
• Sehingga, banyaknya cara adalah 3 × 2 = 6 cara.
• Dengan demikian, banyaknya pembagi dari 12 adalah 6.

16/22
Solusi yang Lebih Baik (lanj.)

• Cara ini juga berlaku untuk nilai N yang lain.


• Misalnya N = 172.872 = 23 × 32 × 74 .
• Berarti banyak pembaginya adalah 4 × 3 × 5 = 60.

17/22
Solusi yang Lebih Baik (lanj.)
• Secara umum, banyaknya pembagi dari:

N = a1p1 × a2p2 × a3p3 × ... × akpk


adalah:

(1 + p1 ) × (1 + p2 ) × (1 + p3 ) × ... × (1 + pk )

• Sehingga, cukup faktorkan N, lalu hitung banyak pembaginya


dengan rumus tersebut.

• Faktorisasi bilangan dapat diimplementasikan dalam O( N).
• 1 detik cukup untuk N sampai 1014 .

18/22
Ajang Pemrograman Kompetitif

• Olimpiade Sains Nasional (OSN) bidang


komputer/informatika merupakan kompetisi tingkat nasional
di Indonesia.
• International Olympiad in Informatics (IOI) merupakan
kompetisi tingkat internasional bagi siswa SMA dari seluruh
dunia.
• Untuk mahasiswa, terdapat ACM International Collegiate
Programming Contest (ACM-ICPC) yang pesertanya terdiri
dari tim-tim beranggotakan tiga orang.

19/22
Manfaat Pemrograman Kompetitif

• Mengasah kemampuan memecahkan masalah.


• Bertemu dengan teman-teman sehobi.
• Biasanya, wawancara untuk masuk ke perusahaan teknologi
terkemuka menggunakan soal-soal pemrograman kompetitif.

20/22
Dan Tentunya...

Menantang dan menyenangkan!

21/22
Penutup

• Sebagai pemanasan, silakan mengerjakan soal-soal latihan


yang diberikan.

22/22
Matematika Diskret Dasar

Tim Olimpiade Komputer Indonesia

1/43
Pendahuluan

Melalui dokumen ini, kalian akan:


• Mempelajari aritmetika modular.
• Mempelajari bilangan prima dan uji keprimaan.
• Mempelajari algoritma prime generation.
• Memahami FPB dan KPK.
• Mempelajari dan memanfaatkan pigeonhole principle.

2/43
Matematika Diskret

• Merupakan cabang matematika yang mempelajari tentang


sifat bilangan bulat, logika, kombinatorika, dan graf.

3/43
Bagian 1

Arimetika Modular

4/43
Konsep Modulo

• Operasi a mod m biasa disebut ”a modulo m”.


• Operasi modulo ini akan memberikan sisa hasil bagi a oleh m.
• Contoh:
• 5 mod 3 = 2
• 10 mod 2 = 0
• 21 mod 6 = 3

5/43
Sifat-Sifat Modulo

• (A + B) mod M = ((A mod M) + (B mod M)) mod M


• (A − B) mod M = ((A mod M) − (B mod M)) mod M
• (A × B) mod M = ((A mod M) × (B mod M)) mod M
• (AB ) mod M = ((A mod M)B ) mod M

6/43
Aplikasi Modulo

• Pada pemrograman kompetitif, tidak jarang kita harus


menghitung n! mod k (terutama dalam kombinatorika).
• Seandainya kita menghitung n!, kemudian menggunakan
operasi mod, kemungkinan besar kita akan mendapatkan
integer overflow.
• Untungnya, kita dapat memanfaatkan sifat modulo.

7/43
Aplikasi Modulo (lanj.)

Solusi:

modularFactorial(n, k)
1 result = 1
2 for i = 1 to n
3 result = (result × i) mod k
4 return result

8/43
Hati-Hati!
• Aritmetika modular tidak serta-merta bekerja pada
pembagian.
• ba mod n 6= a mod n

b mod n mod n.
12 12 mod 6

• Contoh: 4 mod 6 6= 4 mod 6 mod 6.

• Pada aritmetika modular, ba mod n biasa ditulis sebagai:

a × b −1 mod n

dengan b −1 adalah modular multiplicative inverse dari b.


• Jika tertarik, Anda bisa mempelajari modular multiplicative
inverse melalui tautan Wikipedia ini.

9/43
Bagian 2

Bilangan Prima

10/43
Konsep Bilangan Prima

Bilangan Prima
Bilangan bulat positif yang tepat memiliki dua faktor (pembagi),
yaitu 1 dan dirinya sendiri.
Contoh: 2, 3, 5, 13, 97.

Bilangan Komposit
Bilangan yang memiliki lebih dari dua faktor.
Contoh: 6, 14, 20, 25.

11/43
Uji Keprimaan

• Uji keprimaan (primality testing ) adalah algoritma untuk


mengecek apakah suatu bilangan bulat N adalah bilangan
prima.
• Kita dapat memanfaatkan sifat bilangan prima yang
disebutkan pada slide sebelumnya untuk mengecek apakah
suatu bilangan merupakan suatu bilangan prima.

12/43
Uji Keprimaan (lanj.)

• Solusi yang mudah untuk mengecek apakah N prima atau


tidak tentu dengan mengecek apakah ada bilangan selain 1
dan N yang habis membagi N.
• Maka, kita dapat melakukan iterasi dari 2 hingga N − 1 untuk
mengetahui apakah ada bilangan selain 1 dan N yang habis
membagi N.
• Kompleksitas: O(N).

13/43
Uji Keprimaan (lanj.)

isPrimeNaive(n)
1 if n ≤ 1
2 return false

3 for i = 2 to n − 1
4 if n mod i == 0
5 return false

6 return true

14/43
Uji Keprimaan (lanj.)

• Ada solusi yang lebih cepat dari O(N).


• Manfaatkan
√ √ bahwa jika N = a × b, dan a ≤ b, maka
observasi
a≤ N dan b ≥ N.
• Kita tidak perlu memeriksa b; seandainya N habis dibagi b,
tentu N habis dibagi a.

• Jadi kita hanya perlu memeriksa hingga N.

• Kompleksitas: O( N)

15/43
Uji Keprimaan (lanj.)

isPrimeSqrt(n)
1 if n ≤ 1
2 return false

3 i =2
4 while i × i ≤ n
5 if n mod i == 0
6 return false
7 i = i +1

8 return true

16/43
Bagian 3

Prime Generation
(Pembangkitan Bilangan Prima)

17/43
Solusi Naif

• Misalkan kita ingin mengetahui himpunan bilangan prima


yang tidak lebih dari N.
• Kita dapat membangkitkan bilangan prima dengan iterasi dan
uji keprimaan.
• Solusi:
1. Lakukan iterasi dari 2 sampai N
2. Untuk tiap bilangan, cek apakah dia bilangan prima. Jika ya,
kita bisa memasukkannya ke daftar bilangan prima.

18/43
Solusi Naif (lanj.)

simplePrimeGeneration(N)
1 primeList = {}
2 for i = 2 to N
3 if isPrimeSqrt(i)
4 primeList = primeList ∪ {i}
5 return primeList

19/43
Sieve of Eratosthenes

• Terdapat solusi yang lebih cepat untuk membangkitkan


bilangan prima, yaitu Sieve of Eratosthenes.
• Ide utama utama dari algoritma ini adalah mengeliminasi
bilangan-bilangan dari calon bilangan prima.
• Yang akan kita eliminasi adalah bilangan komposit.

20/43
Sieve of Eratosthenes (lanj.)

• Kita tahu bahwa suatu bilangan komposit c dapat dinyatakan


sebagai c = p × q, dengan p suatu bilangan prima.
• Seandainya kita mengetahui suatu bilangan prima, kita dapat
mengeliminasi kelipatan-kelipatan bilangan tersebut dari calon
bilangan prima.
• Contoh: jika diketahui 7 adalah bilangan prima, maka 14, 21,
28, ... dieliminasi dari calon bilangan prima.

21/43
Prosedur Sieve of Eratosthenes

1. Awalnya seluruh bilangan bulat dari 2 hingga N belum


dieliminasi.
2. Lakukan iterasi dari 2 hingga N:
2.1 Jika bilangan ini belum dieliminasi, artinya bilangan ini
merupakan bilangan prima.
2.2 Lakukan iterasi untuk mengeliminasi kelipatan bilangan
tersebut.

22/43
Implementasi Sieve of Eratosthenes

• Kita dapat menggunakan array boolean untuk menyimpan


informasi apakah suatu bilangan telah tereliminasi.
• Jika kita ingin mencari bilangan prima yang ≤ N, maka
diperlukan memori sebesar O(N).
• Melalui perhitungan matematis, kompleksitas waktu solusi ini
adalah O(N log log N).

23/43
Implementasi Sieve of Eratosthenes (lanj.)

sieveOfEratosthenes(N)
1 // Siapkan array boolean eleminated berukuran N
2 // Inisialisasi array eleminated dengan false
3 primeList = {}
4 for i = 2 to n
5 if not eleminated[i]
6 primeList = primeList ∪ {i}
7 j = i ×i
8 while j ≤ n
9 eleminated[j] = true
10 j = j +i
11 return primeList

24/43
Implementasi Sieve of Eratosthenes (lanj.)

• Perhatikan baris ke-7 yang berisi j = i × i.


• Di sini, j menyatakan kelipatan i yang akan dieliminasi.
• Perhatikan bahwa j dimulai dari i × i, bukan 2i.
• Alasannya adalah 2i, 3i, 4i, ..., (i − 1)i pasti sudah tereliminasi
pada iterasi-iterasi sebelumnya.

25/43
Bagian 4

FPB dan KPK

26/43
Faktorisasi Prima

• Ketika masih SD, kita pernah belajar memfaktorkan bilangan


dengan pohon faktor.
• Melalui faktorisasi prima, kita dapat menyatakan suatu
bilangan sebagai hasil perkalian faktor primanya.
• Contoh: 7875 = 32 × 53 × 7 .

27/43
FPB dan KPK

• FPB dan KPK dapat dicari melalui faktorisasi prima.


• Untuk setiap bilangan prima, kita menggunakan pangkat
terkecil untuk FPB dan pangkat terbesar untuk KPK.
• Contoh:
• 4725 = 33 × 52 × 7
• 7875 = 32 × 53 × 7
• Maka:
• FPB(4725, 7875) = 32 × 52 × 7 = 1525
• KPK (4725, 7875) = 33 × 53 × 7 = 23625

a×b
• Terdapat pula sifat KPK (a, b) = FPB(a,b) .

28/43
Algoritma Euclid

• Untuk mencari FPB suatu bilangan, menggunakan pohon


faktor cukup merepotkan.
• Kita perlu mencari faktor prima bilangan tersebut, dan jika
faktor primanya besar, tentu akan menghabiskan banyak
waktu.
• Terdapat algoritma yang dapat mencari FPB(a, b) dalam
O(log (min (a, b))).
• Algoritma ini bernama Algoritma Euclid.

29/43
Algoritma Euclid (lanj.)

euclid(a, b)
1 if b == 0
2 return a
3 else
4 return euclid(b, a mod b)

30/43
Algoritma Euclid (lanj.)

• Sangat pendek!
• Anda dapat menghemat waktu pengetikan kode dalam
melakukan pencarian FPB dengan algoritma ini.
• Jika Anda tertarik dengan pembuktiannya, baca lebih lanjut di
tautan Wikipedia ini.

31/43
Bagian 5

Pigeonhole Principle (PHP)

32/43
Pigeonhole Principle

• Konsep PHP adalah ”Jika ada N ekor burung dan M sangkar,


yang memenuhi N > M , maka pasti ada sangkar yang berisi
setidaknya 2 ekor burung”.
• Secara matematis, jika ada N ekor burung dan M sangkar,
maka pasti ada sangkar yang berisi setidaknya N ekor
 
M
burung.

33/43
Pigeonhole Principle (lanj.)

• Terkesan sederhana?
• Simak contoh aplikasi prinsip ini.

34/43
Contoh Soal PHP

• Pak Dengklek memiliki sebuah array A berisi N bilangan bulat


non-negatif.
• Anda ditantang untuk memilih angka-angka dari array -nya
yang jika dijumlahkan habis dibagi N.
• Angka di suatu indeks array tidak boleh dipilih lebih dari
sekali.
• Apabila mungkin, cetak indeks angka-angka yang Anda ambil.
• Apabila tidak mungkin, cetak ”tidak mungkin”.
• Batasan
• 1 ≤ N ≤ 105
• Array A hanya berisi bilangan bulat non-negatif.

35/43
Analisis Contoh Soal PHP

• Inti soal ini adalah mencari apakah pada array berukuran N,


terdapat subhimpunan tidak kosong yang jumlahan
elemen-elemennya habis dibagi N.

36/43
Analisis Contoh Soal PHP (lanj.)

• Mari kita coba mengerjakan versi lebih mudah dari soal ini:
Bagaimana jika yang diminta subbarisan, bukan subhimpunan?
• Anggap array A dimulai dari indeks 1 (one-based).
k
P
• Misalkan kita memiliki fungsi sum(k) = A[i].
i=1
• Untuk sum(0), sesuai definisi nilainya adalah 0.

37/43
Analisis Contoh Soal PHP (lanj.)

r
P
• Perhatikan bahwa A[i] = sum(r ) − sum(l − 1).
i=l

• Jika subbarisan A[l..r ] habis dibagi N, maka


(sum(r ) − sum(l − 1)) mod N = 0.

• Ini dapat kita tuliskan sebagai


sum(r ) mod N = sum(l − 1) mod N.

38/43
Analisis Contoh Soal PHP (lanj.)

• Observasi 1:
Ada N kemungkinan nilai (sum(x) mod N), yaitu [0..N − 1].
• Observasi 2:
Ada N + 1 nilai x untuk (sum(x) mod N), yaitu untuk
x ∈ [0..N].

Ingat bahwa sum(0) ada agar jumlahan subbarisan A[1..k]


untuk tiap k dapat kita nyatakan dalam bentuk:
(sum(k) − sum(0)).

39/43
Analisis Contoh Soal PHP (lanj.)

Observasi 3:
• Ada N + 1 kemungkinan nilai x
• Ada N kemungkinan nilai sum(x) mod N
• Pasti ada a dan b, sehingga
sum(b) mod N = sum(a) mod N
• Subbarisan yang menjadi solusi adalah A[a + 1..b].

40/43
Analisis Contoh Soal PHP (lanj.)

• Dengan menyelesaikan versi mudah dari soal awal kita,


ternyata kita justru dapat menyelesaikan soal tersebut.
• Suatu subbarisan dari A pasti juga merupakan subhimpunan
dari A.
• Ternyata, selalu ada cara untuk menjawab pertanyaan Pak
Dengklek.
• Yakni, keluaran ”tidak mungkin” tidak akan pernah terjadi.

41/43
Implementasi

findDivisibleSubsequence(A, N)
1 // Inisialisasi array sum[0..N] dengan 0
2 // Isikan nilai sum[i] dengan (A[1] + A[2] + ... + A[i])
3 // Inisialisasi array seenInIndex[0..N − 1] dengan −1

4 for i = 0 to N
5 if seenInIndex[sum[i] mod N] == −1
6 seenInIndex[sum[i] mod N] = i
7 else
8 a = seenInIndex[sum[i] mod N]
9 b=i
10 return [a + 1, a + 2, ..., b]

42/43
Penutup

• Matematika diskret merupakan topik yang sangat luas.


• Topik ini berisikan banyak konsep dasar yang umum
digunakan pada pemrograman kompetitif.

43/43
Matematika Diskret Dasar:
Kombinatorika

Tim Olimpiade Komputer Indonesia

1/48
Pendahuluan

Melalui dokumen ini, kita akan:


• Mempelajari aturan perkalian, penjumlahan, dan
pembagian.
• Mempelajari permutasi.
• Mempelajari kombinasi.
• Memahami segitiga pascal.

2/48
Bagian 1

Aturan Perkalian dan Aturan Penjumlahan

3/48
Contoh Soal 1
• Terdapat 3 buah kota yaitu A, B, dan C.
• Kota A dan kota B terhubung oleh 3 jalur berbeda yaitu e1 ,
e2 , dan e3 .
• Sedangkan kota B dan kota C terhubung oleh 2 jalur berbeda
yaitu e4 dan e5 .
• Berapa banyak cara berbeda untuk menuju kota C dari kota
A?
• Ilustrasi:
e1 e4
e2
A e3
B e5
C

4/48
Solusi Awal
• Apabila kita hitung satu per satu, maka cara yang berbeda
untuk menuju kota C dari kota A adalah sebagai berikut:
• Melalui jalur e1 kemudian jalur e4
• Melalui jalur e1 kemudian jalur e5
• Melalui jalur e2 kemudian jalur e4
• Melalui jalur e2 kemudian jalur e5
• Melalui jalur e3 kemudian jalur e4
• Melalui jalur e3 kemudian jalur e5
• Dengan kata lain, terdapat 6 cara berbeda untuk menuju kota
C dari kota A.
• Tetapi, apabila jumlah kota dan jalur yang ada sangatlah
banyak, kita tidak mungkin menulis satu per satu cara yang
berbeda. Karena itulah kita gunakan aturan perkalian.

5/48
Aturan Perkalian

• Misalkan suatu proses dapat dibagi menjadi N subproses


independen yang mana terdapat ai cara untuk menyelesaikan
subproses ke-i.
• Banyak cara yang berbeda untuk menyelesaikan proses
tersebut adalah a1 × a2 × a3 × ... × ai .

6/48
Solusi dengan Aturan Perkalian

• Anggaplah bahwa perjalanan dari kota A menuju kota B


merupakan subproses pertama, yang mana terdapat 3 cara
untuk menyelesaikan subproses tersebut.
• Anggaplah pula bahwa perjalanan dari kota B menuju kota C
merupakan subproses kedua, yang mana terdapat 2 cara untuk
menyelesaikan subproses tersebut.
• Karena perjalanan dari kota A menuju kota B dan dari kota B
menuju kota C merupakan 2 subproses yang berbeda, maka
kita dapat menggunakan aturan perkalian.
• Banyak cara berbeda dari kota A menuju kota C adalah
3 × 2 = 6.

7/48
Contoh Soal 2
• Contoh soal ini merupakan lanjutan dari Contoh Soal 1.
• Deskripsi soal, jumlah kota dan jalur serta susunan jalur yang
ada sama persis dengan soal tersebut.
• Tambahkan 1 jalur lagi, yaitu e6 yang menghubungkan kota A
dan C.
• Berapa banyak cara berbeda untuk menuju kota C dari kota
A?
• Ilustrasi:
e1 e4
e2
A e3
B e5
C

e6

8/48
Analisis Contoh Soal
• Dengan mencoba satu persatu setiap cara, maka terdapat 7
cara yang berbeda, yaitu 6 cara sesuai dengan soal
sebelumnya, ditambah dengan menggunakan jalur e6 .
• Apabila kita menggunakan aturan perkalian, maka didapatkan
banyak cara yang berbeda adalah 3 × 2 × 1 = 6 yang mana
jawaban tersebut tidaklah tepat.
• Kita tidak dapat menggunakan aturan perkalian dalam
permasalahan ini, karena antara perjalanan dari kota A
menuju kota C melalui kota B dengan tanpa melalui kota B
merupakan 2 proses yang berbeda.
• Oleh karena itu, kita dapat menggunakan aturan
penjumlahan.

9/48
Aturan Penjumlahan

• Misalkan suatu proses dapat dibagi menjadi N himpunan


proses berbeda yaitu H1 , H2 , H3 , ..., HN dengan setiap
himpunannya saling lepas (tidak beririsan).
• Banyak cara yang berbeda untuk menyelesaikan proses
tersebut adalah |H1 | + |H2 | + |H3 | + ... + |HN | dengan |Hi |
merupakan banyaknya cara berbeda untuk menyelesaikan
proses ke-i.

10/48
Solusi dengan Aturan Penjumlahan

• Proses perjalanan dari kota A menuju kota C dapat kita bagi


menjadi 2 himpunan proses yang berbeda, yaitu dari kota A
menuju kota C melalui kota B, dan dari kota A langsung
menuju kota C.
• Banyak cara dari kota A menuju kota C melalui kota B dapat
kita dapatkan dengan aturan perkalian seperti yang dibahas
pada permasalahan sebelumnya, yaitu 6 cara berbeda.
• Banyak cara dari kota A langsung menuju kota C adalah 1
cara, yaitu melalui jalur e6 .
• Dengan aturan penjumlahan, banyak cara berbeda dari kota A
menuju kota C adalah 6 + 1 = 7 cara berbeda.

11/48
Hati-Hati!

• Apabila terdapat irisan dari himpunan proses tersebut, maka


solusi yang kita dapatkan dengan aturan penjumlahan menjadi
tidak tepat, karena ada solusi yang terhitung lebih dari sekali.
• Agar solusi tersebut menjadi tepat, gunakan Prinsip
Inklusi-Eksklusi pada Teori Himpunan.

12/48
Bagian 2

Permutasi

13/48
Perkenalan Permutasi

• Permutasi adalah pemilihan urutan beberapa elemen dari


suatu himpunan.
• Untuk menyelesaikan soal-soal permutasi, dibutuhkan
pemahaman konsep faktorial.

14/48
Notasi Faktorial

• Faktorial dari N (N!) merupakan hasil perkalian dari semua


bilangan asli kurang dari atau sama dengan N.
• N! = N × (N − 1) × (N − 2) × ... × 3 × 2 × 1, dengan 0! = 1.
• Contoh: 5! = 5 × 4 × 3 × 2 × 1 = 120.

15/48
Aturan Pembagian

• Aturan pembagian terkadang disebut juga sebagai


redundansi.
• Apabila terdapat K susunan cara berbeda yang kita anggap
merupakan 1 cara yang sama, maka kita dapat membagi total
keseluruhan cara dengan K , sehingga K cara tersebut
dianggap sama sebagai 1 cara.

16/48
Contoh Aturan Pembagian

• Banyak kata berbeda yang disusun dari huruf-huruf penyusun


”TOKI” adalah 4! (menggunakan aturan perkalian).
• Apabila kita ganti soal tersebut, yaitu kata berbeda yang
disusun dari huruf-huruf penyusun ”BACA”, solusi 4!
merupakan solusi yang salah.
• Sebab, terdapat 2 buah huruf ’A’. Sebagai contoh: BA1 CA2
dan BA2 CA1 pada dasarnya merupakan kata yang sama.

17/48
Contoh Aturan Pembagian (lanj.)

• Terdapat 2! cara berbeda tetapi yang kita anggap sama, yaitu


penggunaan A1 A2 dan A2 A1 .
• Sehingga banyak kata berbeda yang dapat kita bentuk dari
4! 24
huruf-huruf penyusun kata ”BACA” adalah 2! = 2 = 12 kata
berbeda.

18/48
Contoh Soal 1

• Terdapat 5 anak (sebut saja A, B, C, D, dan E) yang sedang


mengikuti sebuah kompetisi.
• Dalam kompetisi tersebut akan diambil 3 peserta sebagai
pemenang.
• Berapa banyak susunan pemenang yang berbeda dari kelima
orang tersebut?

19/48
Solusi Awal

• Anggap bahwa kita mengambil semua anak sebagai


pemenang, sehingga terdapat 5! = 120 susunan pemenang
yang berbeda (ABCDE, ABCED, ABDCE, ..., EDCBA).
• Apabila kita hanya mengambil 3 peserta saja, perhatikan
bahwa terdapat 2 cara berbeda yang kita anggap sama.
Contoh: (ABC)DE dan (ABC)ED merupakan cara yang sama,
karena 3 peserta yang menang adalah A, B, dan C.
• Dengan menggunakan aturan pembagian, maka banyak
5! 120
susunan pemenang yang berbeda adalah 2! = 2 = 60
susunan berbeda.

20/48
Solusi Secara Umum

• Apabila terdapat N anak, dan kita mengambil semua anak


sebagai pemenang, maka terdapat N! susunan cara berbeda.
• Tetapi apabila kita hanya mengambil R anak saja, maka akan
terdapat (N − R)! susunan berbeda yang kita anggap sama.
• Secara umum dengan aturan pembagian, banyak susunan
N!
berbeda adalah (N−R)! .
• Inilah yang kita kenal dengan istilah permutasi.

21/48
Permutasi

• Misalkan terdapat n objek dan kita akan mengambil r objek


dari n objek tersebut yang mana r < n dan urutan
pengambilan diperhitungkan.
• Banyak cara pengambilan yang berbeda adalah permutasi r
terhadap n:
n!
• P(n, r ) =n Pr = Prn = (n−r )! .

22/48
Contoh Soal 2

• Contoh soal ini sejenis dengan contoh pada aturan pembagian.


• Berapa banyak kata berbeda yang disusun dari huruf-huruf
penyusun kata ”MEGAGIGA”?

23/48
Solusi Awal

• Terdapat 8 huruf, sehingga banyak kata yang dapat disusun


adalah 8!.
• Terdapat 3 huruf ’G’ sehingga terdapat 6 kata berbeda yang
kita anggap sama (G1 G2 G3 , G1 G3 G2 , ..., G3 G2 G1 ).
• Dengan aturan pembagian, maka banyak kata yang dapat
8!
disusun mengingat kesamaan kata pada huruf G adalah 3! .
• Perlu kita perhatikan pula bahwa terdapat 2 huruf A,
sehingga dengan cara yang sama akan didapatkan banyak
8!
kata yang berbeda adalah (3!×2!) .

24/48
Solusi Secara Umum

• Terdapat N huruf, sehingga banyak kata yang dapat kita


susun adalah N!.
• Apabila terdapat K huruf dengan setiap hurufnya memiliki Ri
huruf yang sama, maka dengan aturan pembagian banyak
N!
kata berbeda yang dapat disusun adalah (R1 !×R2 !×R3 !×...×RK !)
.
• Inilah yang kita kenal dengan permutasi elemen berulang.

25/48
Permutasi Elemen Berulang

• Misalkan terdapat n objek dan terdapat k objek yang mana


setiap objeknya memiliki ri elemen yang berulang, maka
banyaknya cara berbeda dalam menyusun objek tersebut
adalah:
• Prn1 ,r2 ,r3 ,...,rk = (r !×r !×rn!!×...×r !)
1 2 3 k

26/48
Contoh Soal 3

• Terdapat 4 anak, sebut saja A, B, C, dan D.


• Berapa banyak susunan posisi duduk yang berbeda apabila
mereka duduk melingkar?

27/48
Solusi Awal

• Banyak susunan posisi duduk yang berbeda apabila mereka


duduk seperti biasa (tidak melingkar) adalah 4! = 120.
• Perhatikan bahwa posisi duduk ABCD, BCDA, CDAB, dan
DABC merupakan susunan yang sama apabila mereka duduk
melingkar, karena susunan tersebut merupakan rotasi dari
susunan yang lainnya.
• Dengan kata lain terdapat 4 cara berbeda yang kita anggap
sama.
• Dengan aturan pembagian, banyak susunan posisi duduk yang
120
berbeda adalah 4 = 30 susunan berbeda.

28/48
Solusi Secara Umum

• Banyak susunan posisi duduk yang berbeda apabila N anak


seperti biasa (tidak melingkar) adalah N!.
• Dengan analisis yang sama, akan terdapat N cara berbeda
yang kita anggap sama.
• Dengan aturan pembagian, banyak susunan posisi duduk yang
N!
berbeda adalah N = (N − 1)! susunan berbeda.
• inilah yang kita kenal dengan istilah permutasi siklis.

29/48
Permutasi Siklis

• Permutasi siklis adalah permutasi yang disusun melingkar.


• Banyaknya susunan yang berbeda dari permutasi siklis
terhadap n objek adalah:
n
• P(siklis) = (n − 1)!

30/48
Bagian 3

Kombinasi

31/48
Contoh Soal 1

• Terdapat 5 anak (sebut saja A, B, C, D, dan E) yang mana


akan dipilih 3 anak untuk mengikuti kompetisi.
• Berapa banyak susunan tim berbeda yang dapat dibentuk?

32/48
Solusi Awal
• Soal ini berbeda dengan permutasi, karena susunan ABC dan
BAC merupakan susunan yang sama, yaitu 1 tim terdiri dari
A, B, dan C.
• Apabila kita anggap bahwa mereka merupakan susunan yang
120
berbeda, maka banyaknya susunan tim adalah P25 = 2 = 60.
• Untuk setiap susunan yang terdiri dari anggota yang sama
akan terhitung 6 susunan berbeda yang mana seharusnya
hanya dihitung sebagai 1 susunan yang sama.
• Contoh: ABC, ACB, BAC, BCA, CAB, CBA merupakan 1
susunan yang sama.
• Oleh karena itu dengan aturan pembagian, banyaknya
60
susunan tim yang berbeda adalah 6 = 10 susunan berbeda.

33/48
Solusi Secara Umum

• Misalkan terdapat N anak dan akan kita ambil R anak untuk


dibentuk sebagai 1 tim.
• Apabila urutan susunan diperhitungkan, maka banyaknya
susunan tim adalah PRN .
• Setiap susunan yang terdiri dari anggota yang sama akan
terhitung R! susunan berbeda yang mana seharusnya hanya
dihitung sebagai 1 susunan yang sama.
• Dengan aturan pembagian, banyaknya susunan tim yang
PRN N!
berbeda adalah R! = (N−R)!×R! susunan berbeda.
• Inilah yang kita kenal dengan istilah kombinasi.

34/48
Kombinasi

• Misalkan terdapat n objek dan kita akan mengambil r objek


dari n objek tersebut dengan r < n dan urutan pengambilan
tidak diperhitungkan.
• Banyaknya cara pengambilan yang berbeda adalah kombinasi
r terhadap n:
• C (n, r ) =n Cr = Crn = (n−rn!)!×r ! .

35/48
Contoh Soal 2

• Pak Dengklek ingin membeli kue pada toko kue yang menjual
3 jenis kue, yaitu rasa coklat, stroberi, dan kopi.
• Apabila Pak Dengklek ingin membeli 4 buah kue, maka berapa
banyak kombinasi kue berbeda yang Pak Dengklek dapat beli?

36/48
Analisis Contoh Soal

• Perhatikan bahwa membeli coklat-stroberi dengan


stroberi-coklat akan menghasilkan kombinasi yang sama.
• Contoh soal ini merupakan permasalahan kombinasi.
• Akan tetapi, kita dapat membeli suatu jenis kue beberapa kali
atau bahkan tidak membeli sama sekali.
• Contoh soal ini dapat dimodelkan secara matematis menjadi
mencari banyaknya kemungkinan nilai A, B, dan C yang
memenuhi A + B + C = 4 dan A, B, C ≥ 0.

37/48
Solusi
• Kita dapat membagi 4 kue tersebut menjadi 3 bagian. Untuk
mempermudah ilustrasi tersebut, kita gunakan lambang o
yang berarti kue, dan | yang berarti pembatas.
• Bagian kiri merupakan kue A, bagian tengah merupakan kue
B, dan bagian kanan merupakan kue C.
• Contoh: (o|o|oo) menyatakan 1 kue A, 1 kue B, dan 2 kue
C.
• Contoh lain: (oo|oo|) menyatakan 2 kue A, 2 kue B, dan 0
kue C.
• Dengan kata lain, semua susunan yang mungkin adalah
(oooo||), (ooo|o|), (oo|oo|), ..., (||oooo) yang tidak
6!
lain merupakan C26 = 4!×2! = 15 susunan berbeda.

38/48
Solusi Secara Umum

• Kita ingin mencari banyaknya susunan nilai berbeda dari


X1 , X2 , X3 , ..., Xr yang mana X1 + X2 + X3 + ... + Xr = n.
• Untuk membagi n objek tersebut menjadi r bagian, maka
akan dibutuhkan r − 1 buah pembatas, sehingga akan
terdapat n + r − 1 buah objek, yang mana kita akan memilih
r − 1 objek untuk menjadi simbol |.
• Dengan kata lain, banyaknya susunan nilai yang berbeda
−1
adalah Crn+r
−1 .
• Inilah yang kita kenal dengan istilah kombinasi dengan
perulangan.

39/48
Kombinasi dengan Perulangan

• Misalkan terdapat r jenis objek dan kita akan mengambil n


objek, dengan tiap jenisnya dapat diambil 0 atau beberapa
kali.
• Banyaknya cara berbeda yang memenuhi syarat tersebut
adalah sebagai berikut:
−1 (n+r −1)!
• Cnn+r −1 = Crn+r
−1 = n!×(r −1)!

40/48
Bagian 4

Segitiga Pascal

41/48
Segitiga Pascal

• Segitiga Pascal merupakan susunan dari koefisien-koefisien


binomial dalam bentuk segitiga.
• Nilai dari baris ke-n suku ke-r adalah Crn .
• Contoh Segitiga Pascal:
• Baris ke-1: 1
• Baris ke-2: 1 1
• Baris ke-3: 1 2 1
• Baris ke-4: 1 3 3 1
• Baris ke-5: 1 4 6 4 1

42/48
Analisis

• Diberikan suatu himpunan S = {X1 , X2 , ..., Xn }. Berapa


banyak cara untuk memilih r objek dari S?
• Terdapat 2 kasus:
• Kasus 1: Xn dipilih. Artinya, r − 1 objek harus dipilih dari
himpunan {X1 , X2 , X3 , ..., Xn−1 }. Banyaknya cara berbeda dari
kasus ini adalah Crn−1
−1 .
• Kasus 2: Xn tidak dipilih. Artinya, r objek harus dipilih dari
himpunan {X1 , X2 , X3 , ..., Xn }. Banyaknya cara berbeda dari
kasus ini adalah Crn−1 .

43/48
Analisis (lanj.)

• Dengan aturan penjumlahan dari kasus 1 dan kasus 2, kita


dapatkan Crn = Crn−1 n−1 .
−1 + Cr
• Persamaan itulah yang sering kita gunakan dalam membuat
Segitiga Pascal, karena Crn juga menyatakan baris ke-n dan
kolom ke-r pada Segitiga Pascal.

44/48
Analisis (lanj.)

• Dalam dunia pemrograman, kadang kala dibutuhkan


perhitungan seluruh nilai Crn yang memenuhi n ≤ N, untuk
suatu N tertentu.
• Pencarian nilai dari Crn dengan menghitung faktorial memiliki
kompleksitas O(n).
• Apabila seluruh nilai kombinasi dicari dengan cara tersebut,
kompleksitas akhirnya adalah O(N 3 ).
• Namun, dengan menggunakan persamaan
Crn = Crn−1 n−1 , maka secara keseluruhan kompleksitasnya
−1 + Cr
dapat menjadi O(N 2 ).

45/48
Implementasi Segitiga Pascal (lanj.)

segitiga pascal(N)
1 // Sediakan array 2-dimensi C berukuran (N + 1) × (N + 1)
2 for i = 0 to N
3 C [i][0] = 1
4 for j = 0 to i − 1
5 C [i][j] = C [i − 1][j − 1] + C [i − 1][j]
6 C [i][i] = 1

46/48
Penggunaan Segitiga Pascal

• Dalam bidang matematika, Segitiga Pascal merupakan


kumpulan dari koefisien binomial yang dapat digunakan dalam
n
Binomial Newton (x + y )n = ar x n−r y r yang mana ar
P
r =0
merupakan bilangan dalam segitiga pascal baris ke-n suku
ke-r .
• Dalam bidang programming, algoritma dari segitiga pascal
dapat digunakan untuk mencari semua nilai dari kombinasi r
terhadap n untuk seluruh n ≤ N dengan kompleksitas waktu
O(N 2 ) dan memori O(N 2 ).

47/48
Penutup

• Materi ini berisi mengenai kombinatorika dasar dan


implementasinya yang umum digunakan dalam pemrograman
kompetitif.
• Dengan materi ini, Anda diharapkan dapat menggunakan
konsep kombinatorika yang ada.

48/48
Brute Force

Tim Olimpiade Komputer Indonesia

1/20
Pendahuluan

Melalui dokumen ini, kalian akan:


• Mempelajari konsep brute force.
• Mampu mengerjakan persoalan dengan pendekatan brute
force.

2/20
Konsep

• Brute force bukan suatu algoritma khusus, melainkan suatu


strategi penyelesaian masalah.
• Sebutan lainnya adalah complete search dan exhaustive
search.
• Prinsip dari strategi ini hanya satu, yaitu...

3/20
Konsep (lanj.)

coba semua kemungkinan!

4/20
Sifat Brute Force

• Brute force menjamin solusi pasti benar, karena seluruh


kemungkinan dijelajahi.
• Akibatnya, umumnya brute force bekerja dengan lambat.
• Terutama ketika banyak kemungkinan solusi yang perlu
dicoba.

5/20
Soal: Subset Sum

• Diberikan N buah bilangan {a1 , a2 , ..., aN } dan bilangan K .


• Apakah terdapat subhimpunan sedemikian sehingga jumlahan
dari elemen-elemennya sama dengan K ?
• Bila ya, maka keluarkan ”YA”. Selain itu keluarkan ”TIDAK”.

Batasan:
• 1 ≤ N ≤ 15
• 1 ≤ K ≤ 109
• 1 ≤ ai ≤ 109

6/20
Solusi

• Untuk setiap elemen, kita memiliki 2 pilihan, yaitu memilih


elemen tersebut atau tidak memilihnya.
• Kita akan menelusuri semua kemungkinan pilihan.
• Jika jumlahan dari elemen-elemen yang dipilih sama dengan
K , maka terdapat solusi.
• Hal ini dapat dengan mudah diimplementasikan secara
rekursif.

7/20
Performa?

• Terdapat 2N kemungkinan konfigurasi ”pilih/tidak pilih”.


• Kompleksitas solusi adalah O(2N ).
• Untuk nilai N terbesar, 2N = 215 = 32.768.
• Masih jauh di bawah 100 juta, yaitu banyaknya operasi
komputer perdetik pada umumnya.

8/20
Implementasi

solve(i, sum)
1 if i > N
2 return (sum == K )

3 option1 = solve(i + 1, sum + ai ) // Pilih elemen ai


4 option2 = solve(i + 1, sum) // Tidak pilih elemen ai
5 return option1 or option2

solveSubsetSum()
1 return solve(1, 0)

9/20
Optimisasi

• Bisakah solusi tersebut menjadi lebih cepat?


• Perhatikan kasus ketika nilai sum telah melebihi K .
• Karena semua ai bernilai positif, maka sum tidak akan
mengecil.
• Karena itu, bila sum sudah melebihi K , dipastikan tidak akan
tercapai sebuah solusi.

10/20
Solusi Teroptimisasi

solve(i, sum)
1 if i > N
2 return (sum == K )

3 if sum > K
4 return false

5 option1 = solve(i + 1, sum + ai ) // Pilih elemen ai


6 option2 = solve(i + 1, sum) // Tidak pilih elemen ai
7 return option1 or option2

11/20
Pruning

Hal ini biasa disebut sebagai pruning (pemangkasan).


Pruning
Merupakan optimisasi dengan mengurangi ruang pencarian dengan
cara menghindari pencarian yang sudah pasti salah.

12/20
Pruning (lanj.)

• Meskipun mengurangi ruang pencarian, pruning umumnya


tidak mengurangi kompleksitas solusi.
• Sebab, biasanya terdapat kasus yang mana pruning tidak
mengurangi ruang pencarian secara signifikan.
• Pada kasus ini, solusi dapat dianggap tetap bekerja dalam
O(2N ).

13/20
Soal: Mengatur Persamaan

• Diberikan sebuah persamaan: p + q + r = 0.


• Masing-masing dari p, q, dan r harus merupakan anggota dari
himpunan bilangan yang unik {a1 , a2 , ..., aN }
• Berapa banyak triplet (p, q, r ) berbeda yang memenuhi
persamaan tersebut?
Batasan:
• 1 ≤ N ≤ 2.000
• −105 ≤ ai ≤ 105

14/20
Solusi Sederhana

countTriplets()
1 count = 0
2 for i = 1 to N
3 for j = 1 to N
4 for k = 1 to N
5 p = ai
6 q = aj
7 r = ak
8 if (p + q + r ) == 0
9 count = count + 1
10 return count

15/20
Solusi Sederhana (lanj.)

• Kompleksitas waktu solusi ini adalah O(N 3 ).


• Tentunya terlalu besar untuk nilai N mencapai 2.000.
• Ada solusi yang lebih baik?

16/20
Observasi

• Jika kita sudah menentukan nilai p dan q, maka nilai r


haruslah −(p + q).
• Jadi cukup tentukan nilai p dan q, lalu periksa apakah nilai
−(p + q) ada pada bilangan-bilangan yang diberikan.
• Pemeriksaan ini dapat dilakukan dengan binary search.
• Kompleksitas solusi menjadi O(N 2 log N)

17/20
Solusi Lebih Baik

countTripletsFast()
1 count = 0
2 for i = 1 to N
3 for j = 1 to N
4 p = ai
5 q = aj
6 r = −(p + q)
7 if exists(r )
8 count = count + 1
9 return count

dengan exists(r ) adalah algoritma binary search untuk memeriksa


keberadaan r di {a1 , a2 , ..., aN } (tentunya setelah diurutkan).

18/20
Solusi Lebih Baik (lanj.)

• Kompleksitas O(N 2 log N) sudah cukup untuk N yang


mencapai 2.000.
• Dari sini kita belajar bahwa optimisasi pada pencarian kadang
diperlukan, meskipun ide dasarnya adalah brute force.

19/20
Penutup

• Ide dari brute force biasanya sederhana: Anda hanya perlu


menjelajahi seluruh kemungkinan solusi.
• Biasanya merupakan ide pertama yang didapatkan saat
menghadapi masalah.
• Lakukan analisis algoritma, jika kompleksitasnya cukup, maka
brute force saja :)
• Bila tidak cukup cepat, coba lakukan observasi.
• Bisa jadi kita dapat melakukan brute force dari ”sudut
pandang yang lain” dan lebih cepat.
• Bila tidak berhasil juga, baru coba pikirkan strategi lainnya.

20/20
Divide and Conquer

Tim Olimpiade Komputer Indonesia

1/37
Perkenalan

• Terkadang, permasalahan lebih mudah diselesaikan jika dibagi


menjadi beberapa masalah yang lebih kecil.
• Masalah lebih kecil kemudian diselesaikan secara independen.
• Hasilnya digabungkan menjadi solusi untuk masalah yang
lebih besar.
• Strategi ini digunakan Belanda pada masa penjajahan, biasa
dipelajari pada sejarah sebagai devide et impera.

2/37
Konsep

Secara umum, divide and conquer terdiri dari tiga tahap:


• divide: membagi masalah yang besar menjadi
masalah-masalah yang lebih kecil.
• conquer: ketika sebuah masalah sudah cukup kecil untuk
diselesaikan, langsung selesaikan.
• combine: menggabungkan solusi dari masalah-masalah yang
lebih kecil menjadi solusi untuk masalah yang besar.

3/37
Ilustrasi Konsep

divide

conquer

combine

4/37
Studi Kasus 1: Merge Sort

Merge sort: algoritma pengurutan O(N log N).

Prinsip kerja algoritma ini adalah:


• divide: jika array yang akan diurutkan berukuran besar, bagi
menjadi dua array sama besar.
• conquer : ketika array sudah cukup kecil untuk diurutkan,
lakukan pengurutan.
• combine: dari dua array yang telah terurut, gabungkan
menjadi sebuah array terurut.

5/37
Contoh Eksekusi Merge Sort

Kapan suatu array dianggap cukup kecil untuk dapat diurutkan


secara langsung?

• Sederhana, yaitu ketika panjang array itu tinggal satu.


• Tentu saja, array dengan panjang satu sudah pasti terurut.

Jadi selama array yang hendak diurutkan masih memiliki panjang


lebih dari satu, bagi array itu menjadi dua.

6/37
Contoh Eksekusi Merge Sort (lanj.)

Array yang dimiliki masih terlalu besar, belah menjadi dua.

5 2 7 6 1 8 9 3

5 2 7 6 1 8 9 3

7/37
Contoh Eksekusi Merge Sort (lanj.)

Masih belum cukup kecil, belah lagi.

5 2 7 6 1 8 9 3

5 2 7 6 1 8 9 3

8/37
Contoh Eksekusi Merge Sort (lanj.)

Masih belum cukup kecil, belah lagi.

5 2 7 6 1 8 9 3

5 2 7 6 1 8 9 3

9/37
Contoh Eksekusi Merge Sort (lanj.)

Kini kita memiliki array -array yang panjangnya hanya satu.

Secara definisi, masing-masing array telah terurut.

5 2 7 6 1 8 9 3

10/37
Contoh Eksekusi Merge Sort (lanj.)

Gabungkan hasil pembelahan sebelumnya.

5 2 7 6 1 8 9 3

2 5 6 7 1 8 3 9

11/37
Contoh Eksekusi Merge Sort (lanj.)

Gabungkan lagi.

2 5 6 7 1 8 3 9

2 5 6 7 1 3 8 9

12/37
Contoh Eksekusi Merge Sort (lanj.)

Gabungkan lagi dan akhirnya didapatkan array terurut.

2 5 6 7 1 3 8 9

1 2 3 5 6 7 8 9

13/37
Menggabungkan Dua Array Terurut

Bagaimana cara menggabungkan dua array yang telah terurut?

2 5 6 7 1 3 8 9

14/37
Menggabungkan Dua Array Terurut (lanj.)

Observasi: elemen terkecil dari array gabungan pasti salah satu


dari elemen terkecil array yang terurut. Lebih tepatnya, yang
memiliki nilai lebih kecil.

2 5 6 7 1 3 8 9

15/37
Menggabungkan Dua Array Terurut (lanj.)

Ulangi hal serupa sampai salah satu atau kedua array habis.

2 5 6 7 3 8 9

16/37
Menggabungkan Dua Array Terurut (lanj.)

5 6 7 3 8 9

1 2

17/37
Menggabungkan Dua Array Terurut (lanj.)

5 6 7 8 9

1 2 3

18/37
Menggabungkan Dua Array Terurut (lanj.)

6 7 8 9

1 2 3 5

19/37
Menggabungkan Dua Array Terurut (lanj.)

7 8 9

1 2 3 5 6

20/37
Menggabungkan Dua Array Terurut (lanj.)

Ketika salah satu array telah habis, array yang masih bersisa
tinggal ditempelkan di akhir array gabungan.

8 9

1 2 3 5 6 7

21/37
Menggabungkan Dua Array Terurut (lanj.)

Selesai proses menggabungkan.

1 2 3 5 6 7 8 9

22/37
Analisis Menggabungkan Dua Array Terurut

• Misalkan kedua array terurut yang akan digabung adalah A


dan B.
• Pada setiap langkah, salah satu dari elemen A atau B
dipindahkan.
• Total terdapat |A| + |B| proses, sehingga kompleksitasnya
O(|A| + |B|).

23/37
Analisis Algoritma Merge Sort

• Misalkan N menyatakan ukuran dari array .


• Dengan sifat membagi dua secara terus-menerus, kedalaman
rekursif dari merge sort adalah O(log N)
• Untuk setiap kedalaman, dilakukan aktivitas divide dan
combine.
• Proses divide dan proses conquer selalu bekerja dalam O(1).

24/37
Analisis Algoritma Merge Sort (lanj.)

1
2
3
4

• Kedalaman 1, proses combine bekerja dalam O(2 × N2 )


• Kedalaman 2, proses combine bekerja dalam O(4 × N4 )
• Kedalaman 3, proses combine bekerja dalam O(8 × N8 )
• ...

25/37
Analisis Algoritma Merge Sort (lanj.)

• Mudah untuk disadari bahwa keseluruhan proses untuk setiap


kedalaman bekerja dalam O(N).
• Karena kedalaman maksimal adalah O(log N), kompleksitas
akhir merge sort adalah O(N log N).
• Jauh lebih cepat dari algoritma pengurutan O(N 2 ) seperti
bubble sort.
• Merge sort mampu mengurutkan array dengan ratusan ribu
elemen dalam waktu singkat.

26/37
Contoh Implementasi
Mengurutkan arr [left..right]:

mergeSort(arr [], left, right)


1 if left == right
2 // Tinggal 1 elemen, sudah pasti terurut
3 else
4 mid = (left + right) div 2
5 mergeSort(arr , left, mid)
6 mergeSort(arr , mid + 1, right)
7 merge(arr , left, mid, mid + 1, right)

27/37
Contoh Implementasi (lanj.)
Menggabungkan arr [aLeft..aRight] dengan arr [bLeft..bRight] yang
telah terurut:

merge(arr [], aLeft, aRight, bLeft, bRight)


1 // Buat array penampungan sementara bernama temp
2 // Isikan temp[aLeft . . bRight] dengan nilai dari arr [aLeft . . bRight]
3 tIndex = 1
4 aIndex = aLeft
5 bIndex = bLeft
6 ...

28/37
Contoh Implementasi (lanj.)

5 ...
6 // Selama kedua subarray masih ada isinya, ambil yang terkecil
7 while (aIndex ≤ aRight) and (bIndex ≤ bRight)
8 if temp[aIndex] < temp[bIndex]
9 arr [tIndex] = temp[aIndex]
10 aIndex = aIndex + 1
11 else
12 arr [tIndex] = temp[bIndex]
13 bIndex = bIndex + 1
14 tIndex = tIndex + 1
15 ...

29/37
Contoh Implementasi (lanj.)

14 ...
15 // Masukkan subarray yang masih bersisa
16 // Hanya salah satu dari kedua while ini yang akan dieksekusi
17 while (aIndex ≤ aRight)
18 arr [tIndex] = temp[aIndex]
19 aIndex = aIndex + 1
20 tIndex = tIndex + 1
21 while (bIndex ≤ bRight)
22 arr [tIndex] = temp[bIndex]
23 bIndex = bIndex + 1
24 tIndex = tIndex + 1
25 // selesai penggabungan

30/37
Catatan Tentang Merge Sort

• Merge sort memiliki sifat stable.


• Artinya jika dua elemen a1 dan a2 memenuhi:
• memiliki yang nilai sama, dan
• sebelum diurutkan a1 terletak sebelum a2 ,
maka setelah diurutkan a1 tetap terletak sebelum a2 .

31/37
Studi Kasus 2: Mencari Nilai Terbesar

• Diberikan sebuah array A yang memiliki N bilangan.


• Cari nilai terbesar yang ada pada A!

• Masalah ini mudah diselesaikan dengan perulangan biasa.


• Namun, coba kita selesaikan dengan divide and conquer .

32/37
Studi Kasus 2: Mencari Nilai Terbesar (lanj.)

Pertama kita definisikan tahap-tahapnya:


• Divide: jika array berukuran besar, bagi menjadi dua subarray .
• Conquer : ketika array hanya berisi satu elemen, nilai
terbesarnya pasti elemen tersebut.
• Combine: nilai terbesar dari array adalah maksimum dari nilai
terbesar di subarray pertama dan nilai terbesar di subarray
kedua.

33/37
Contoh Implementasi

findMax(arr [], left, right)


1 if left == right
2 return arr [left]
3 else
4 mid = (left + right) div 2
5 leftMax = findMax(arr , left, mid)
6 rightMax = findMax(arr , mid + 1, right)
7 return max(leftMax, rightMax)

34/37
Analisis Algoritma Mencari Nilai Terbesar
O(1)

O(1) O(1)

O(1) O(1) O(1) O(1)

O(1) O(1) O(1) O(1) O(1) O(1) O(1) O(1)

• Setiap operasi divide, conquer, dan combine bekerja dalam


O(1).
• Ketiga operasi tersebut dilaksanakan sebanyak
1 + 2 + 4 + 8 + ... + 2L kali, dengan 2L mendekati N.
• Sehingga totalnya dilaksanakan sekitar 2L+1 − 1 = 2N operasi.

35/37
Analisis Algoritma Mencari Nilai Terbesar (lanj.)

• Kompleksitas akhirnya adalah O(N).


• Ternyata, strategi ini tidak lebih baik dari mencari nilai
maksimum satu per satu.
• Kesimpulannya, divide and conquer tidak selalu dapat
mengurangi kompleksitas solusi naif.

36/37
Penutup

• Divide and conquer merupakan salah satu strategi dalam


penyelesaian masalah.
• Konsep ini banyak digunakan pada struktur data lanjutan dan
geometri komputasional.
• Jika suatu masalah dapat dibelah menjadi beberapa masalah
yang lebih kecil dan serupa, kemudian hasil dari
masing-masing penyelesaiannya dapat digabungkan, maka
divide and conquer dapat digunakan.

37/37
Divide and Conquer:
Quicksort

Tim Olimpiade Komputer Indonesia

1/31
Pengenalan

• Selain merge sort, ada algoritma pengurutan yang bekerja


dalam O(N log N), salah satunya quicksort.
• Quicksort menggunakan prinsip divide and conquer dalam
pengurutan.

2/31
Konsep

• Misalkan kita hendak mengurutkan array bilangan secara


menaik.
• Pilih salah satu elemen, misalnya 5.

5 2 7 6 1 8 9 3

3/31
Konsep (lanj.)

• Tempatkan seluruh elemen yang ≤ 5 di bagian kiri array , dan


yang > 5 di bagian kanan.
• Urutan elemennya setelah pemindahan tidak penting.

2 1 3 5 7 6 8 9

4/31
Konsep (lanj.)

• Lakukan quicksort serupa untuk bagian kiri dan kanan secara


rekursif.
• Suatu ketika, seluruh array menjadi terurut.

2 1 3 5 7 6 8 9

5/31
Konsep (lanj.)

Jika dikaitkan dengan divide and conquer :


• divide: partisi array menjadi dua seperti yang dijelaskan
sebelumnya.
• conquer : ketika array tinggal satu elemen, berarti sudah
terurut.
• combine: tempelkan hasil quicksort bagian kiri dan kanan.

6/31
Partisi

• Bagian utama dari quicksort adalah proses partisi (bagian


divide).
• Sebelum melakukan partisi, pilih suatu elemen yang akan
dijadikan pivot (=pijakan).
• Nantinya, akan dilakukan partisi supaya seluruh elemen yang
≤ pivot berada di bagian kiri, dan yang > pivot di bagian
kanan.
• Untuk saat ini, kita akan menggunakan elemen di tengah
array pivot.

7/31
Partisi (lanj.)

• Kini sudah ditentukan nilai pivot, bagaimana cara mempartisi


array ?
• Kita dapat menggunakan sebuah perulangan O(N) untuk
menampung hasil partisi di suatu array sementara, lalu
menempatkan kembali hasil partisi ke array sebenarnya.
• Namun cara ini agak merepotkan, kita perlu membuat array
sementara dan memindahkan isi array .

8/31
Partisi Hoare

• Ada beberapa algoritma untuk melakukan partisi secara in


place, yaitu tanpa array sementara.
• Kita akan menggunakan salah satunya, yaitu algoritma partisi
Hoare.

9/31
Partisi Hoare (lanj.)

Misalkan pivot = 5.
Mulai dengan dua variabel penunjuk, kiri dan kanan di
ujung-ujung array .

2 5 7 4 6 1 3 8 9
^
kiri
^
kanan

10/31
Partisi Hoare (lanj.)

Gerakkan variabel kiri ke arah kanan, sampai elemen yang ditunjuk


tidak < pivot

2 5 7 4 6 1 3 8 9
^
kiri
^
kanan

11/31
Partisi Hoare (lanj.)

Gerakkan variabel kanan ke arah kiri, sampai elemen yang ditunjuk


tidak > pivot

2 5 7 4 6 1 3 8 9
^
kiri
^
kanan

12/31
Partisi Hoare (lanj.)

Tukar elemen yang ditunjuk kiri dan kanan, lalu gerakkan:


• kiri ke kanan satu langkah
• kanan ke kiri satu langkah

2 5 7 4 6 1 3 8 9
^ kanan
kiri
^

13/31
Partisi Hoare (lanj.)

Karena kiri ≤ kanan, artinya partisi belum selesai.


Kita akan mengulangi hal yang serupa.

2 5 3 4 6 1 7 8 9
^ kanan
kiri
^

14/31
Partisi Hoare (lanj.)

Gerakkan variabel kiri.

2 5 3 4 6 1 7 8 9
^ ^
kiri kanan

15/31
Partisi Hoare (lanj.)

Gerakkan variabel kanan.


Kebetulan, elemen yang ditunjuk sudah tidak > pivot.

2 5 3 4 6 1 7 8 9
^ ^
kiri kanan

16/31
Partisi Hoare (lanj.)

Tukar dan gerakkan variabel kiri dan kanan satu langkah.

2 5 3 4 6 1 7 8 9
^ ^
kanan kiri

17/31
Partisi Hoare (lanj.)

Kini sudah tidak kiri ≤ kanan, artinya partisi selesai.

2 5 3 4 1 6 7 8 9
^ ^
kanankiri

18/31
Partisi Hoare (lanj.)

Perhatikan bahwa seluruh elemen yang ≤ pivot berada di kiri, dan


sisanya di kanan.

2 5 3 4 1 6 7 8 9
^ ^
kanan kiri

19/31
Implementasi Partisi Hoare

partition(arr [], left, right, pivot)


1 pLeft = left
2 pRight = right
3 while pLeft ≤ pRight
4 while arr [pLeft] < pivot
5 pLeft = pLeft + 1
6 while arr [pRight] > pivot
7 pRight = pRight − 1
8 if pLeft ≤ pRight
9 swap(arr [pLeft], arr [pRight])
10 pLeft = pLeft + 1
11 pRight = pRight − 1

20/31
Analisis Algoritma Partisi Hoare

• Terdapat dua variabel penunjuk, yang setiap langkahnya selalu


bergerak ke satu arah tanpa pernah mundur.
• Algoritma berhenti ketika variabel kiri dan kanan bertemu.
• Artinya, setiap elemen array dikunjungi tepat satu kali.
• Kompleksitasnya adalah O(N).

21/31
Integrasi ke Quicksort
Setelah kita mengimplementasikan algoritma partisi,
mengintegrasikan ke quicksort cukup mudah.
quicksort(arr [], left, right)
1 if left ≥ right
2 // Tidak ada elemen yang perlu diurutkan
3 else
4 pivot = arr [(left + right) div 2]

5 // ... sisipkan isi algoritma Hoare di sini ...


6 // Sampai saat ini, dipastikan pRight < pLeft

7 quicksort(left, pRight)
8 quicksort(pLeft, right)

22/31
Analisis Algoritma Quicksort

• Pada setiap kedalaman rekursi, array hasil partisi belum tentu


memiliki ukuran yang sama.
• Hasil partisi bergantung pada nilai pivot yang kita pilih.
• Kita anggap dulu hasil partisi selalu membelah array menjadi
dua subarray sama besar.

23/31
Analisis Algoritma Quicksort (lanj.)

• Ternyata analisisnya mirip seperti merge sort.


• Kompleksitasnya adalah O(N log N)

24/31
Analisis Algoritma Quicksort: Kasus Terbaik

• Pembelahan menjadi dua subarray sama besar menjamin


kedalaman rekursif sedangkal mungkin.
• Sehingga untuk kasus terbaik, jalannya algoritma menjadi
seperti merge sort dan bekerja dalam O(N log N).

1
2
3
4

25/31
Analisis Algoritma Quicksort: Kasus Rata-Rata
• Pada kebanyakan kasus, ukuran hasil partisi berbeda.
• Secara rata-rata kompleksitasnya masih dapat dianggap
O(N log N).

26/31
Analisis Algoritma Quicksort: Kasus Terburuk
• Kasus paling buruk: ukuran hasil partisi sangat timpang.
• Akibatnya, kedalaman rekursif mendekati N.
• Kompleksitasnya menjadi O(N 2 ).

27/31
Analisis Algoritma Quicksort (lanj.)

• Tidak perlu khawatir, peluang terjadinya kasus terburuk


sangat-sangat-amat-lah kecil.
• Artinya, pada sebagian besar kasus, quicksort akan berjalan
dengan sangat cepat.

28/31
Pemilihan Pivot

Terdapat beberapa strategi pemilihan pivot untuk mencegah hasil


partisi yang terlalu timpang:
• Pilih salah satu elemen secara acak, ketimbang selalu memilih
elemen di tengah.
• Pilih median dari elemen paling depan, tengah, dan paling
belakang.

29/31
Stable Sort

• Quicksort memiliki sifat tidak stable.


• Artinya jika dua elemen a1 dan a2 memenuhi:
• memiliki yang nilai sama, dan
• sebelum diurutkan a1 terletak sebelum a2 ,
maka setelah diurutkan tidak dijamin a1 tetap terletak
sebelum a2 .

30/31
Penutup

• Terdapat jiwa divide and conquer pada algoritma quicksort.


• Kita telah mempelajari algoritma pengurutan yang efisien
lainnya.

31/31
Greedy

Tim Olimpiade Komputer Indonesia

1/32
Pendahuluan

Melalui dokumen ini, kalian akan:


• Memahami konsep greedy .
• Menyelesaikan beberapa contoh persoalan greedy sederhana.

2/32
Greedy

Greedy merupakan sebuah teknik dalam strategi penyelesaian


masalah, bukan suatu algoritma khusus.

3/32
Konsep Greedy

Suatu persoalan dapat diselesaikan dengan teknik greedy jika


persoalan tersebut memiliki memiliki sifat berikut:
• Solusi optimal dari persoalan dapat ditentukan dari solusi
optimal subpersoalan tersebut.
• Pada setiap subpersoalan, ada suatu langkah yang bisa
dilakukan yang mana langkah tersebut menghasilkan solusi
optimal pada subpersoalan tersebut. Langkah ini disebut juga
greedy choice.

4/32
Contoh Soal: Activity Selection

• Diberikan N buah aktivitas.


• Aktivitas ke-i dinyatakan dalam (ai .start, ai .end).
• Artinya, aktivitas ini dimulai pada waktu ai .start dan berakhir
pada waktu ai .end.
• Pada setiap satuan waktu, Anda dapat mengikuti paling
banyak satu aktivitas.
• Anda ingin mengatur jadwal sedemikian sehingga Anda bisa
ikut aktivitas sebanyak mungkin.

5/32
Contoh Activity Selection

• Sebagai contoh, diberikan 4 buah aktivitas:


[(1, 3), (2, 6), (5, 7), (8, 9)].
• Anda dapat hadir di 3 aktivitas berbeda yang tidak saling
tindih, yaitu (1, 3), (5, 7), dan(8, 9).

1 2 3 4 5 6 7 8 9

6/32
Solusi Activity Selection
• Misalkan kegiatan pertama yang kita ikuti adalah kegiatan
ke-x.
• Kegiatan selanjutnya yang diikuti haruslah memiliki waktu
awal ≥ ax .end.
• Lebih jauh lagi, ternyata kita mendapat persoalan yang
serupa, hanya saja ukurannya lebih kecil.
• Dengan kata lain, kita memperoleh subpersoalan.

sub-persoalan

^ ^
ax.start ax.end

7/32
Solusi Activity Selection (lanj.)

Pertanyaan: aktivitas mana yg akan pertama kali dipilih?

Perhatikan pilihan berikut:


• Memilih aktivitas dengan waktu mulai paling awal.
• Memilih aktivitas dengan durasi paling singkat.
• Memilih aktivitas dengan waktu akhir paling awal.

8/32
Memilih Aktivitas Pertama

Memilih aktivitas dengan waktu mulai paling awal:


• Bisa jadi ada aktivitas yang mulai lebih awal, tetapi memiliki
durasi yang sangat panjang sehingga menyita waktu.
• Memilih aktivitas yang mulai paling awal belum pasti optimal.

9/32
Memilih Aktivitas Pertama (lanj.)

Memilih aktivitas dengan durasi paling singkat:


• Bisa jadi aktivitas dengan durasi paling singkat ini memotong
dua aktivitas lain yang sebenarnya dapat kita ikuti.
• Pilihan ini juga belum pasti menghasilkan solusi optimal.

10/32
Memilih Aktivitas Pertama (lanj.)
Memilih aktivitas dengan waktu akhir paling awal:
• Dengan memilih aktivitas yang selesai lebih awal, kita
mempunyai sisa waktu lebih banyak untuk aktivitas lainnya.
• Tanpa peduli kapan aktivitas ini mulai atau berapa durasinya,
memilih yang selesai lebih awal pasti menguntungkan.
• Pilihan ini adalah merupakan greedy choice, yang selalu
menghasilkan solusi optimal.

11/32
Penyelesaian Activity Selection
• Kini kita dapat menentukan aktivitas yang akan diikuti
pertama kali.
• Selanjutnya kita mendapatkan subpersoalan, yang ternyata
dapat diselesaikan dengan cara serupa!

sub-persoalan

^ ^
ax.start ax.end

12/32
Contoh Eksekusi Activity Selection

Berikut contoh cara pemilihan aktivitas yang optimal.

13/32
Contoh Eksekusi Activity Selection (lanj.)

Dimulai dari memilih aktivitas pertama.

14/32
Contoh Eksekusi Activity Selection (lanj.)

Selanjutnya kita mendapatkan subpersoalan.


Beberapa aktivitas kini tidak dapat dipilih lagi.

15/32
Contoh Eksekusi Activity Selection (lanj.)

Masalah yang kita hadapi serupa dengan masalah sebelumnya.


Kita tinggal memilih aktivitas yang berakhir paling awal.

16/32
Contoh Eksekusi Activity Selection (lanj.)

Kembali kita mendapatkan subpersoalan....

17/32
Contoh Eksekusi Activity Selection (lanj.)

Pilih lagi aktivitas yang berakhir paling awal.

18/32
Contoh Eksekusi Activity Selection (lanj.)

Didapatkan lagi subpersoalan....

19/32
Contoh Eksekusi Activity Selection (lanj.)

Pilih lagi aktivitas yang berakhir paling awal.

20/32
Contoh Eksekusi Activity Selection (lanj.)

Selesai!
Tidak ada cara lain yang memberikan hasil lebih optimal.

21/32
Implementasi Solusi Activity Selection

solveActivitySelection(a[], N)
1 // Urutkan a secara menaik berdasarkan a[i].end
2 sortByEndingTime(a, N)

3 selectedCount = 0
4 startTime = 1
5 for i = 1 to N
6 if (a[i].start >= startTime)
7 selectedCount = selectedCount + 1
8 startTime = a[i].end + 1
9 return selectedCount

22/32
Analisis Kompleksitas

• Mengurutkan aktivitas berdasarkan waktu berakhirnya dapat


dilakukan dalam O(N log N).
• Setelah diurutkan, pemilihan aktivitas dapat dilakukan dalam
O(N).
• Kompleksitas akhirnya O(N log N).
• Cepat dan efisien!

23/32
Selingan

• Greedy choice memungkinkan kita untuk memilih suatu


keputusan yang dijamin akan menghasilkan solusi optimal,
tanpa peduli ke depannya seperti apa.
• Hal ini memberi kesan ”rakus”, yaitu hanya mementingkan
masalah yang sedang dihadapi dan selalu mengambil
keputusan terbaik saat ini.
• Inilah sebabnya teknik ini dinamakan greedy .

24/32
Permasalahan pada Algoritma Greedy

Perhatikan contoh soal berikut:


• Anda ingin menukar uang Rp12.000 dengan lembaran uang
kertas Rp5.000, Rp2.000, dan Rp1.000.
• Anda ingin menukar dengan jumlah lembaran sesedikit
mungkin.

25/32
Permasalahan pada Algoritma Greedy

• Greedy choice yang terpikirkan adalah dengan memilih


lembarandengan nominal terbesar yang mungkin untuk tiap
subpersoalan.
• Pertama kita pilih lembaran 5000, sehingga tersisa 7000 lagi
yang harus dipecah.
• Selanjutnya kita pilih 5000 lagi dan menyisakan 2000 untuk
dipecah.
• Akhirnya, kita pilih 2000 sebagai pecahan terakhir.
• Solusi dari kasus ini adalah dengan menggunakan 3 lembaran.

26/32
Permasalahan pada Algoritma Greedy (lanj.)

Dengan soal yang sama, bagaimana jika lembaran rupiah yang


beredar bernilai Rp5.000, Rp4.000, dan Rp1.000?

27/32
Permasalahan pada Algoritma Greedy (lanj.)

• Dengan algoritma greedy , kita akan menukar 12000 dengan


lembaran 5000, 5000, 1000, dan 1000.
• Padahal ada solusi yang lebih baik, yaitu menggunakan 3
lembaran 4000.
• Pada kasus tersebut, greedy choice yang tidak selalu dapat
menghasilkan solusi optimal.
• Permasalahan ini tidak dapat diselesaikan oleh algoritma
greedy .
• (Permasalahan ini bisa diselesaikan dengan algoritma dynamic
programming, yang akan dibahas pada materi berikutnya.)

28/32
Permasalahan pada Algoritma Greedy (lanj.)

• Pembuktian kebenaran algoritma greedy tidaklah mudah.


• Biasanya akan ada beberapa pilihan greedy choice yang ada,
yang mana tidak semuanya bisa menghasilkan solusi optimal.
• Ketika menemukan suatu greedy choice, sangat dianjurkan
untuk menguji kebenaran dari pilihan tersebut sebelum
diimplementasikan.

29/32
Permasalahan pada Algoritma Greedy (lanj.)

• Pengujian yang dapat dilakukan adalah dengan mencoba


membuat contoh kasus yang dapat menggagalkan greedy
choice tersebut.
• Teknik ini biasa disebut proof by counterexample.
• Jika ditemukan satu saja contoh kasus yang mana greedy
choice yang diajukan tidak menghasilkan solusi optimal, maka
greedy choice tersebut dinyatakan salah.
• Namun, bila Anda tidak bisa menemukan counterexample,
belum tentu algoritma Anda benar.

30/32
Saran

• Algoritma greedy terkadang mudah untuk dipikirkan dan


mudah untuk diimplementasikan, namun sulit untuk
dibuktikan kebenarannya.
• Pembuktian kebenaran algoritma greedy bisa jadi
membutuhkan pembuktian matematis yang kompleks dan
memakan waktu.
• Pada suasana kompetisi, intuisi dan pengalaman sangat
membantu untuk menyelesaikan soal bertipe greedy .
• Berhati-hati dan teliti saat mengerjakan soal bertipe greedy .
Perhatikan setiap detil yang ada, karena bisa berakibat fatal.

31/32
Penutup

• Untuk dapat menguasai greedy , Anda perlu banyak berlatih


dan berpikir secara cerdik.
• Selamat berlatih untuk mengasah ”kerakusan” Anda :)

32/32
Dynamic Programming

Tim Olimpiade Komputer Indonesia

1/41
Pendahuluan

Melalui dokumen ini, kalian akan:


• Memahami konsep dynamic programming (DP).
• Menyelesaikan contoh persoalan DP sederhana.

2/41
Motivasi

• Diberikan M jenis koin, masing-masing jenis bernilai


a1 , a2 , ..., aM rupiah.
• Asumsikan banyaknya koin untuk setiap nominal yang ada tak
terbatas.
• Tentukan banyaknya koin paling sedikit untuk membayar
tepat sebesar N rupiah!
• (Persoalan ini biasa disebut dengan coin change.)

3/41
Solusi Greedy

• Mari kita coba menyelesaikan masalah ini secara greedy .


• Salah satu algoritma greedy yang mungkin adalah dengan
selalu menggunakan koin terbesar yang ≤ sisa uang yang
harus dibayar.

4/41
Solusi Greedy (lanj.)

• Misalkan kita memiliki nominal koin 1 rupiah, 6 rupiah, dan


10 rupiah dan ingin membayar 12 rupiah.
• Dengan algoritma sebelumnya, kita akan menggunakan koin
10 rupiah terlebih dahulu.
• Karena tersisa 2 rupiah, berikutnya kita akan menggunakan 2
koin 1 rupiah, sehingga totalnya kita menggunakan 3 koin.
• Namun, ada solusi lebih baik: 2 koin 6 rupiah.
• Algoritma greedy ini tidak memberikan solusi terbaik.

5/41
Observasi

• Anggaplah kita membayar N rupiah dengan koin-koin


satu-persatu.
• Pastilah terdapat koin pertama yang kita bayarkan.
• Jika nilai koin itu adalah ak , maka sisa uang yang perlu kita
bayar adalah N − ak .
• Dalam kasus ini, terdapat M pilihan koin untuk ak .

6/41
Observasi (lanj.)

• Perhatikan bahwa penukaran N − ak merupakan suatu


subpersoalan yang serupa dengan persoalan awalnya.
• Artinya, cara yang sama untuk menyelesaikan subpersoalan
dapat digunakan.
• Kita akan menggunakan strategi penyelesaian secara rekursif.

7/41
Solusi Rekursif

• Definisikan sebuah fungsi f (x) sebagai banyaknya koin


minimum yang dibutuhkan untuk membayar tepat x rupiah.
• Kita dapat mencoba-coba satu koin yang ingin kita gunakan.
• Jika suatu koin ak digunakan, maka kita membutuhkan
f (x − ak ) koin ditambah satu koin ak .
• Atau dapat ditulis f (x) = f (x − ak ) + 1
• Pencarian nilai f (x − ak ) dilakukan secara rekursif, kita
kembali mencoba-coba koin yang ingin digunakan.

8/41
Solusi Rekursif (lanj.)

• Dari semua kemungkinan ak , mana pilihan yang terbaik?


• Pilihan yang terbaik akan memberikan nilai f (x − ak ) + 1
sekecil mungkin.
• Jadi kita cukup mencoba semua kemungkinan ak , dan ambil
yang hasil f (x − ak ) + 1 terkecil.

9/41
Solusi Rekursif (lanj.)

• Jika f (x) dihitung secara rekursif, apa yang menjadi base


case?
• Kasus terkecilnya adalah f (0), yang artinya kita hendak
membayar 0 rupiah.
• Membayar 0 rupiah tidak membutuhkan satu pun koin,
sehinga f (0) = 0.

10/41
Solusi Rekursif (lanj.)

Secara matematis, hubungan rekursif ini dituliskan:



0, x =0
f (x) =
min1≤k≤M,ak ≤x f (x − ak ) + 1, x > 0

11/41
Implementasi Solusi Rekursif
Kita implementasikan f (x) sebagai fungsi solve(x):

solve(x)
1 if (x == 0)
2 return 0
3
4 best = ∞
5 for k = 1 to M
6 if (ak ≤ x)
7 best = min(best, solve(x − ak ) + 1)
8 return best

Jawaban akhirnya ada pada solve(N).

12/41
Solusi Rekursif (lanj.)
Mari kita lihat pohon rekursi yang dihasilkan oleh fungsi f .
Berikut untuk f (12) dengan nominal koin 1, 6, dan 10 rupiah.
f (12)

f (2) f (6) f (11)

f (1) f (0) f (5)

f (0) f (4) f (1) f (5) f (10)

... f (0) f (4)


f (0) f (4) f (9)
...
... ... ...

13/41
Solusi Rekursif (lanj.)

• Jika diperhatikan pada pohon rekursi, terdapat O(M) cabang


untuk setiap pemanggilan f .
• Untuk menghitung nilai f (N), kita akan memiliki pohon
rekursi yang kira-kira sedalam O(N).
• Berarti kira-kira dilakukan O(M N ) pemanggilan fungsi.
• Karena itu, solusi ini membutuhkan O(M N ) operasi, yang
mana banyaknya operasi ini eksponensial.

14/41
Solusi Rekursif (lanj.)

• Biasanya solusi eksponensial berjalan sangat lambat.


• Cobalah Anda hitung nilai O(M N ) dengan M = 3 dan
N = 100, untuk menyadari betapa lambatnya solusi ini!
• Kita tidak ingin memiliki solusi eksponensial pada
pemrograman kompetitif, kecuali pada soal-soal tertentu yang
tidak memiliki solusi polinomial.
• Karena itu, kita harus melakukan sebuah optimisasi.

15/41
Optimisasi
Jika diperhatikan, ternyata banyak f (x) yang dihitung berkali-kali.
Sebagai contoh, f (5) dan f (4).

f (12)

f (2) f (6) f (11)

f (1) f (0) f (5)

f (0) f (4) f (1) f (5) f (10)

... f (0) f (4)


f (0) f (4) f (9)
...
... ... ...

16/41
Optimisasi (lanj.)

• Perhatikan bahwa hanya ada N + 1 kemungkinan x untuk


f (x), yaitu 0 sampai N.
• Kita dapat melakukan memoisasi, yaitu mencatat hasil
perhitungan f (x) setelah menghitungnya.
• Jika suatu ketika kita kembali memerlukan nilai f (x), kita
tidak perlu menghitungnya kembali.

17/41
Solusi Rekursif dengan Memoisasi
solve(x)
1 if (x == 0)
2 return 0
3 if computed[x]
4 return memo[x] // Langsung kembalikan
5
6 best = ∞
7 for k = 1 to M
8 if (ak ≤ x)
9 best = min(best, solve(x − ak ) + 1)
10 computed[x] = true // Tandai bahwa sudah pernah dihitung
11 memo[x] = best
12 return best

18/41
Solusi Rekursif dengan Memoisasi (lanj.)

• Untuk menghitung suatu nilai f (x), kita membutuhkan O(M)


iterasi.
• Sehingga untuk menghitung nilai f (x) untuk seluruh x, kita
membutuhkan O(NM) operasi.
• Banyaknya operasi ini polinomial terhadap N dan M, dan
jauh lebih cepat daripada solusi rekursif sebelumnya.

19/41
Dynamic Programming

• Merupakan metode penyelesaian persoalan yang melibatkan


pengambilan keputusan dengan memanfaatkan informasi dari
penyelesaian subpersoalan yang sama namun lebih kecil.
• Solusi subpersoalan tersebut hanya dihitung satu kali dan
disimpan di memori.
• Jika sebuah persoalan adalah masalah optimisasi, maka
biasanya kita mencoba semua kemungkinan solusi sub-problem
yang dihasilkan, dan mengambil yang hasilnya paling optimal.

20/41
Dynamic Programming

Terdapat dua cara mengimplementasikan DP


• top-down: diimplementasikan secara rekursif sambil mencatat
nilai yang sudah ditemukan (memoisasi).
• bottom-up: diimplementasikan secara iteratif dengan
menghitung mulai dari kasus yang kecil ke besar.

21/41
Top-Down

• Cara yang sebelumnya kita gunakan adalah top-down.


• Kata memoisasi berasal dari ”memo”, yang artinya catatan.
• Pada top-down, penyelesaian masalah dimulai dari kasus yang
besar.
• Untuk menyelesaikan kasus yang besar, dibutuhkan solusi dari
kasus yang lebih kecil.
• Karena solusi kasus yang lebih kecil belum ada, maka kita
akan mencarinya terlebih dahulu, lalu mencatat hasilnya.
• Hal ini dilakukan secara rekursif.

22/41
Bottom Up

• Pada bottom-up, penyelesaian masalah dimulai dari kasus


yang kecil.
• Ketika merumuskan formula rekursif, kita mengetahui jawaban
kasus yang paling kecil, yaitu base case.
• Informasi ini digunakan untuk menyelesaikan kasus yang
lebih besar.
• Biasanya dianalogikan dengan pengisian ”tabel DP”.
• Hal ini dilakukan secara iteratif.

23/41
Solusi dengan Bottom-Up
Secara bottom-up, kita hitung semua nilai f (x) untuk
semua nilai x dari 0 sampai N secara menaik.
Nilai dari f (x) disimpan dalam array f [x].

solve()
1 f [0] = 0
2 for x = 1 to N
3 best = ∞
4 for k = 1 to M
5 if (ak ≤ x)
6 best = min(best, f [x − ak ] + 1)
7 f [x] = best
8 return f [N]

24/41
Kompleksitas?

• Dengan mudah Anda dapat memperhatikan bahwa


kompleksitas solusi dengan bottom-up adalah O(NM).
• Kompleksitas ini sama seperti dengan cara top-down.
• Kenyataannya, sebenarnya keduanya merupakan algoritma
yang sama, hanya berbeda di arah pencarian jawaban.

25/41
Mengisi ”Tabel”

Cara bottom-up yang dijelaskan sebelumnya terkesan seperti


”mengisi tabel”.

x 0 1 2 3 4 5 6 7 8 9 10 11 12
f (x)

26/41
Mengisi ”Tabel” (lanj.)

Awalnya, diisi f (0) = 0.

x 0 1 2 3 4 5 6 7 8 9 10 11 12
f (x) 0

27/41
Mengisi ”Tabel” (lanj.)

Berikutnya, diisi f (1).


Satu-satunya pilihan adalah menukarkan dengan koin 1, karena
kita tidak bisa menggunakan koin 6 atau 10. Jadi:
f (1) = f (1 − 1) + 1 = f (0) + 1

x 0 1 2 3 4 5 6 7 8 9 10 11 12
f (x) 0 1

Masuk akal, untuk membayar 1 memang kita membutuhkan 1 koin.

28/41
Mengisi ”Tabel” (lanj.)

Hal serupa terjadi ketika kita mengisi f (2), f (3), f (4), danf (5).
Satu-satunya pilihan adalah menukarkan dengan koin 1, karena
kita tidak bisa menggunakan koin 6 atau 10.

x 0 1 2 3 4 5 6 7 8 9 10 11 12
f (x) 0 1 2 3 4 5

29/41
Mengisi ”Tabel” (lanj.)

Berikutnya adalah mengisi f (6).


Terdapat pilihan untuk menggunakan koin 1 atau 6 terlebih
dahulu, sehingga:

f (6) = min(f (6 − 1) + 1, f (6 − 6) + 1)
= min(f (5) + 1, f (0) + 1)
= min(5 + 1, 0 + 1)
= min(6, 1)
=1

30/41
Mengisi ”Tabel” (lanj.)

Memang benar, untuk membayar 6 kita hanya membutuhkan 1


koin.

x 0 1 2 3 4 5 6 7 8 9 10 11 12
f (x) 0 1 2 3 4 5 1

31/41
Mengisi ”Tabel” (lanj.)

Lakukan hal serupa untuk x = 7 sampai x = 9.

x 0 1 2 3 4 5 6 7 8 9 10 11 12
f (x) 0 1 2 3 4 5 1 2 3 4

32/41
Mengisi ”Tabel” (lanj.)

Berikutnya adalah mengisi f (10).


Terdapat pilihan untuk menggunakan koin 1, 6, atau 10 terlebih
dahulu, sehingga:

f (10) = min(f (10 − 1) + 1, f (10 − 6) + 1, f (10 − 10) + 1)


= min(f (9) + 1, f (4) + 1, f (0) + 1)
= min(4 + 1, 4 + 1, 0 + 1)
= min(5, 5, 1)
=1

33/41
Mengisi ”Tabel” (lanj.)

Kembali, memang benar bahwa untuk membayar 10 kita hanya


membutuhkan 1 koin, yaitu langsung menggunakan koin 10
(tanpa 1 dan 6).

x 0 1 2 3 4 5 6 7 8 9 10 11 12
f (x) 0 1 2 3 4 5 1 2 3 4 1

34/41
Mengisi ”Tabel” (lanj.)

Berikutnya adalah mengisi f (11).

f (11) = min(f (11 − 1) + 1, f (11 − 6) + 1, f (11 − 10) + 1)


= min(f (10) + 1, f (5) + 1, f (1) + 1)
= min(1 + 1, 5 + 1, 1 + 1)
= min(2, 6, 2)
=2

x 0 1 2 3 4 5 6 7 8 9 10 11 12
f (x) 0 1 2 3 4 5 1 2 3 4 1 2

35/41
Mengisi ”Tabel” (lanj.)
Terakhir, isi f (12).

f (12) = min(f (12 − 1) + 1, f (12 − 6) + 1, f (12 − 10) + 1)


= min(f (11) + 1, f (6) + 1, f (2) + 1)
= min(2 + 1, 1 + 1, 2 + 1)
= min(3, 2, 3)
=2

Dari sini, terlihat bahwa menggunakan koin 10 terlebih dahulu


(pilihan paling kanan) mengakibatkan banyaknya koin yang
dibutuhkan adalah 3.

Sementara menggunakan koin 6 terlebih dahulu (pilihan tengah)


mengakibatkan banyaknya koin yang dibutuhkan adalah 2.

36/41
Mengisi ”Tabel” (lanj.)

Jadi kita selesai mengisi ”tabel” DP.

x 0 1 2 3 4 5 6 7 8 9 10 11 12
f (x) 0 1 2 3 4 5 1 2 3 4 1 2 2

• Jika Anda menggunakan top-down, pada akhirnya tabel


memo juga akan berisi nilai-nilai ini.
• Coba implementasikan secara top-down dan bottom-up dan
lihat hasilnya!

37/41
Top-Down dan Bottom-Up

Top-down
• Sebuah transformasi natural dari formula rekursif, biasanya
mudah diimplementasikan.
• Urutan pengisian tabel tidak penting.
• Hanya menghitung nilai dari fungsi jika hanya diperlukan.
• Ketika seluruh tabel memo pada akhirnya terisi, bisa saja lebih
lambat karena adanya overhead pemanggilan fungsi.

38/41
Top-Down dan Bottom-Up (lanj.)

Bottom-up
• Tidak mengalami perlambatan dari overhead pemanggilan
fungsi.
• Memungkinkan penggunaan teknik DP lanjutan seperti flying
table, kombinasi dengan struktur data tree, dsb.
• Harus memikirkan urutan pengisian nilai tabel.
• Semua tabel harus diisi nilainya walaupun tidak dibutuhkan
akhirnya.

39/41
Top-Down dan Bottom-Up (lanj.)

• Beberapa orang lebih alami untuk menggunakan top-down,


sementara sisanya lebih terbiasa dengan bottom-up.
• Bergantung dari cara berpikir Anda, salah satunya mungkin
lebih mudah Anda pelajari.
• Untuk orang yang telah berpengalaman, penggunaan
bottom-up dan top-down dapat disesuaikan dengan soal yang
dihadapi.

40/41
Penutup

• Terdapat dua versi DP, yaitu top-down dan bottom-up.


• Keduanya memiliki keuntungan dan kerugian, pilih yang tepat
sesuai dengan kebutuhan soal.
• Kunci dari mengerjakan soal DP adalah mengidentifikasi
pilihan keputusan yang bisa diambil, dan merumuskannya
menjadi rumus rekursif.
• Anda perlu banyak latihan soal DP untuk menjadi terbiasa
dengan melakukan formulasi rekursif ini.

41/41
Dynamic Programming:
Studi Kasus

Tim Olimpiade Komputer Indonesia

1/29
Pendahuluan

Melalui dokumen ini, kalian akan:


• Menyelesaikan beberapa contoh persoalan DP sederhana.
• Membiasakan diri untuk ”berpikir secara DP”.

2/29
Contoh Soal 1: Knapsack

• Diberikan N buah barang, dinomori dari 1 sampai N.


• Barang ke-i memiliki harga vi rupiah dan berat wi gram.
• Kita memiliki tas yang berkapasitas G gram.
• Kita ingin memasukkan beberapa barang kedalam tas,
sedemikian sehingga jumlah berat dari barang-barang yang
kita masukan tidak lebih dari kapasitas tas dan jumlah
harganya sebanyak mungkin.

3/29
Observasi

• Untuk setiap barang, kita harus memutuskan apakah barang


ini diambil atau tidak.
• Jika diambil, kapasitas tas kita berkurang, dan harga barang
yang kita dapatkan bertambah.
• Jika tidak diambil, tidak terjadi perubahan.

4/29
Formulasi

• Definisikan sebuah fungsi dp(i, c) sebagai jumlah harga


maksimum yang mungkin diperoleh, jika kita hanya
mempunyai barang ke-1 sampai ke-i dan sisa kapasitas tas
kita adalah c gram.
• Untuk menghitung fungsi dp(i, c) kita bisa mencoba-coba
apakah kita akan memasukkan barang ke-i ke tas atau tidak.

5/29
Formulasi Rekurens

• Jika kita memasukkan barang ke-i ke tas, maka kita akan


menyisakan barang ke-1 sampai ke-(i − 1) dan sisa kapasitas
tas menjadi c − wi .
• Harga barang yang didapatkan pada kasus ini adalah
dp(i − 1, c − wi ) ditambah dengan harga yang kita peroleh
pada barang ke-i.
• Dapat dituliskan dp(i, c) = dp(i − 1, c − wi ) + vi .
• Kasus ini hanya boleh dipertimbangkan jika c ≥ wi .

6/29
Formulasi Rekurens (lanj.)

• Jika kita tidak memasukkan barang ke-i ke tas, maka kita


akan menyisakan barang ke-1 sampai ke-(i − 1) dan sisa
kapasitas tas masih tetap c.
• Harga barang didapatkan pada kasus ini adalah dp(i − 1, c),
tanpa tambahan apapun (kita tidak mengambil barang ke-i).
• Dapat dituliskan dp(i, c) = dp(i − 1, c).

7/29
Formulasi Rekurens (lanj.)

• Dari kedua pilihan keputusan tersebut, kita tertarik dengan


yang menghasilkan nilai terbesar.
• Cukup bandingkan mana yang lebih besar, antara:
• dp(i − 1, c − wi ) + vi , atau
• dp(i − 1, c)
• Dapat dituliskan:
dp(i, c) = max(dp(i − 1, c − wi ) + vi , dp(i − 1, c)).

• Kembali ditekankan bahwa pilihan memasukkan barang ke-i


hanya boleh dipertimbangkan jika c ≥ wi .

8/29
Formulasi Base Case

• Jika i = 0, maka berarti tidak ada lagi barang yang tersedia.


• Ini berarti dp(i, c) = 0.
• Kasus ini menjadi base case.

9/29
Formulasi Akhir

dp(i, c) dapat dirumuskan sebagai berikut:



 0, i =0
dp(i, c) = dp(i − 1, c), i > 0 ∧ c < wi
max(dp(i − 1, c − wi ) + vi , dp(i − 1, c)), i > 0 ∧ c ≥ wi

10/29
Analisis Kompleksitas

• Terdapat O(N) nilai berbeda untuk nilai i dan O(G ) nilai


berbeda untuk nilai c pada dp(i, c).
• Dibutuhkan O(1) untuk menghitung dp(i, c).
• Sehingga untuk menghitung seluruh nilai dp(i, c) untuk
seluruh i dan c dibutuhkan waktu O(NG ).

11/29
Solusi Top-Down
Kita implementasikan dp(i, c) sebagai fungsi solve(i, c):

solve(i, c)
1 if (i == 0)
2 return 0
3 if computed[i][c]
4 return memo[i][c]
5 best = solve(i − 1, c)
6 if (c ≥ w [i])
7 best = max(best, solve(i − 1, c − w [i]) + v [i])
8 computed[i][c] = true
9 memo[i][c] = best
10 return best

Jawaban akhirnya ada pada solve(N, G ).

12/29
Solusi Bottom-Up
solve()
1 // Base case
2 for c = 0 to G
3 dp[0][c] = 0

4 // Isi ”tabel” dari kasus yang kecil ke besar


5 for i = 1 to N
6 for c = 0 to G
7 best = dp[i − 1][c]
8 if (c ≥ w [i])
9 best = max(best, dp[i − 1][c − w [i]] + v [i])
10 dp[i][c] = best

11 return dp[N][G ]

13/29
Contoh Soal 2: Memotong Kayu
Diadopsi dari UVa 10003 - Cutting Sticks

• Kita akan memotong sebuah batang kayu dengan panjang M


meter pada N titik menjadi N + 1 bagian.
• Titik ke-i berada di Li meter dari ujung kiri, dengan
1 ≤ i ≤ N.
• Untuk memotong sebatang kayu menjadi dua, kita
memerlukan usaha sebesar panjang kayu yang sedang kita
potong.
• Cari urutan pemotongan sedemikian sehingga total usaha
yang dibutuhkan minimum!

14/29
Contoh Soal 2: Memotong Kayu (lanj.)

Sebagai contoh, terdapat sebuah kayu dengan panjang 10 meter


dan terdapat 3 titik potongan pada 2 meter, 4 meter, dan 7 meter
dari ujung kiri.

2 4 7

15/29
Contoh Soal 2: Memotong Kayu (lanj.)
Kita bisa memotong pada titik 2, titik 4, lalu titik 7 dan
memerlukan usaha 10 + 8 + 6 = 24.

2 4 7

+10

+8

+6

16/29
Contoh Soal 2: Memotong Kayu (lanj.)
Cara lain adalah memotong pada titik 4, titik 2, lalu titik 7 dan
memerlukan usaha 10 + 4 + 6 = 20.
2 4 7

+10

+4

+6

17/29
Solusi Greedy?

• Apakah strategi greedy dengan memotong


”setengah-tengahnya” selalu menghasilkan solusi optimal?
• Bagaimana dengan kasus jika M = 2000 dan
L = [1, 2, 3, 4, 5, 1000]?
• Kita akan coba menggunakan DP untuk persoalan ini.

18/29
Observasi

• Untuk pemotongan pertama, terdapat N pilihan lokasi


pemotongan.
• Jika kita memotong di posisi Lm , maka didapatkan dua
batang.
• Batang pertama perlu dipotong di titik L1 , L2 , ..., Lm−1 dan
batang kedua di Lm+1 , Lm+2 , ..., LN .
• Ternyata kita mendapatkan sub-persoalan yang serupa.
• Pemotongan bisa dilanjutkan secara rekursif, dan kita pilih
posisi pemotongan yang ke depannya membutuhkan usaha
terkecil.

19/29
Formulasi Rekurens

• Definisikan sebuah fungsi dp(l, r ) sebagai jumlah usaha


minimum yang mungkin diperoleh, jika kita hanya perlu
memotong di Ll , Ll+1 , ..., Lr .
• Untuk menghitung dp(l, r ) kita dapat mencoba titik mana
yang kita potong pertama kali.
• Jika kita memotong di Lm (l ≤ m ≤ r ), maka kita akan
mendapatkan dua potongan.

l-1 l m-1 m m+1 r r+1


... ...

20/29
Formulasi Rekurens (lanj.)

l-1 l m-1 m m+1 r r+1


... ...
• Total usaha yang dibutuhkan jika kita melakukan pemotongan
di Lm adalah jumlah dari:
• Total usaha minimum dari potongan pertama, yaitu
dp(l, m − 1).
• Total usaha minimum dari potongan kedua, yaitu dp(m + 1, r ).
• Usaha untuk pemotongan ini, yaitu Lr +1 − Ll−1 .
• Untuk mempermudah, asumsikan L0 = 0 dan LN+1 = M.

21/29
Formulasi Base Case

• Ketika l > r , artinya sudah tidak ada pemotongan yang perlu


dilakukan.
• Dengan demikian, usaha yang dibutuhkan adalah 0, atau
dp(l, r ) = 0.

22/29
Formulasi Akhir

Dapat dirumuskan:

0, l >r
dp(l, r ) =
minl≤m≤r dp(l, m − 1) + dp(m + 1, r ) + (Lr +1 − Ll−1 ), l ≤ r

23/29
Analisis Kompleksitas

• Terdapat O(N) nilai berbeda untuk nilai l dan O(N) nilai


berbeda untuk nilai r pada dp(l, r ).
• Dibutuhkan iterasi sebanyak O(N) untuk menghitung dp(l, r ).
• Sehingga untuk menghitung seluruh nilai dp(l, r ) untuk
seluruh l dan r dibutuhkan waktu O(N 3 ).

24/29
Solusi Top-Down
Kita implementasikan dp(l, r ) sebagai fungsi solve(l, r ):
solve(l, r )
1 if (l > r )
2 return 0
3 if computed[l][r ]
4 return memo[l][r ]
5
6 best = ∞
7 cost = L[r + 1] − L[l − 1]
8 for m = l to r
9 best = min(best, solve(l, m − 1) + solve(m + 1, r ) + cost)
10 computed[l][r ] = true
11 memo[l][r ] = best
12 return best
Jawaban akhirnya ada pada solve(1, N).

25/29
Solusi Bottom Up
solve()
1 // Base case
2 for l = 0 to N + 1
3 for r = 0 to l − 1
4 dp[l][r ] = 0

5 // Isi ”tabel” mulai dari kasus yang kecil


6 for gap = 0 to N
7 for l = 1 to N − gap
8 r = l + gap
9 best = ∞
10 cost = L[r + 1] − L[l − 1]
11 for m = l to r
12 best = min(best, dp[l][m − 1] + dp[m + 1][r ] + cost)
13 dp[l][r ] = best

14 return dp[1][N]
26/29
Pengisian ”Tabel” DP

• Perhatikan bahwa pada metode bottom-up, pengisian ”tabel”


dilakukan secara ”tidak biasa”.
• Kita perlu mengisi mulai dari:
• dp[1][1], dp[2][2], ..., dp[N][N],
• lalu dp[1][2], dp[2][3], ..., dp[N − 1][N],
• lalu dp[1][3], dp[2][4], ..., dp[N − 2][N],
• lalu dp[1][4], dp[2][5], ..., dp[N − 3][N],
• dan seterusnya sampai dp[1][N].
• Ingat bahwa pengisian ”tabel” harus dilakukan dari kasus
yang kecil ke besar.
• Definisi ”kasus kecil” pada masalah ini adalah kayu dengan
titik-titik pemotongan yang lebih sedikit.

27/29
Pengisian ”Tabel” DP (lanj.)

• Dari contoh ini kita mempelajari bahwa urutan pengisian


”tabel” pada DP bottom-up tidak selalu biasa.
• Jika urutan pengisiannya salah, maka hasil akhir yang
didapatkan juga bisa jadi salah.
• Hal in terjadi ketika kita hendak menyelesaikan kasus yang
besar, tetapi hasil untuk kasus-kasus yang lebih kecil belum
tersedia.
• Untuk mengetahui urutan pengisian ”tabel”, Anda perlu
mengamati apa definisi ”kasus kecil” pada masalah yang
dihadapi.

28/29
Penutup

• DP merupakan topik yang cukup luas untuk dibicarakan.


• Banyak berlatih mengerjakan soal DP dapat melatih kita
untuk mendapatkan rumus DP yang sesuai dengan masalah
yang dihadapi.
• Topik optimisasi lainnya pada DP akan dibahas pada
kesempatan yang lain.

29/29
Struktur Data Dasar

Tim Olimpiade Komputer Indonesia

1/47
Pendahuluan

Melalui dokumen ini, kalian akan:


• Mengenal beberapa macam struktur data dasar.
• Mengetahui pentingnya penggunaan struktur data.
• Mengetahui operasi-operasi yang dapat dilakukan pada
struktur data dasar.

2/47
Tentang Struktur Data

Struktur Data
Merupakan tata cara untuk merepresentasikan dan menyimpan
data, sehingga mendukung operasi terhadap data tersebut secara
efisien.

3/47
Kilas Balik: Array

Array merupakan contoh struktur data dasar yang mendukung


operasi berikut:
• Membaca nilai yang disimpan pada suatu indeks sembarang.
• Mengubah nilai yang disimpan pada suatu indeks sembarang.

Akses indeks secara sembarang ini biasa disebut sebagai random


access.

4/47
Kilas Balik: Array (lanj.)

• Bagaimana kalau kita hendak menyisipkan suatu elemen


sebagai indeks pertama dari array ?
• Kita harus menggeser seluruh isi array, barulah memasukkan
elemen yang hendak dimasukkan di indeks pertama.
• Operasi ini dilaksanakan dalam O(N), dengan N adalah
ukuran array.

5/47
Kilas Balik: Array (lanj.)

• Bagaimana jika operasi ini sering dilakukan?


• Melaksanakannya dalam O(N) kurang efisien.
• Adakah cara yang lebih baik?

6/47
Bagian 1

Linked List

7/47
Mengenal Linked List
Linked list terdiri dari kumpulan node.

Node dapat diartikan sebagai sebuah titik yang nantinya dapat


dihubungkan dengan node lainnya.

Sebuah node menyimpan dua informasi:


1. data: informasi yang disimpan.
2. next: pointer ke node berikutnya.

data next

8/47
Mengenal Linked List (lanj.)

• Pointer -pointer ini menghubungkan antar node dalam linked


list.
• Node paling depan biasa disebut head.

head
30 1 20 16

9/47
Jenis Linked List

Berdasarkan hubungannya dengan node lain, linked list terbagi


menjadi 2 macam, yaitu:
• singly linked list: tiap node hanya memiliki pointer ke node
selanjutnya saja (next).
• doubly linked list: tiap node memiliki pointer ke node
selanjutnya (next) dan node sebelumnya (prev).

Pada pembahasan ini, kita akan menggunakan doubly linked list.

10/47
Struktur Doubly Linked List

prev data next

head tail
30 1 20 16

11/47
Linked List dan Array
• Pada doubly linked list, biasanya kita hanya memiliki referensi
ke head dan tail.
• Untuk mengakses elemen ke-x dari linked list, kita dapat
melakukannya dengan:

get(head, x)
1 current = head
2 for i = 2 to x
3 current = current.next
4 return current

• Terlihat tidak efisien?

12/47
Linked List dan Array (lanj.)

Doubly linked list memiliki keuntungan dalam:


• Menyisipkan elemen baru.
• Menghapus suatu elemen.

Kedua operasi tersebut dapat dilakukan secara efisien.

13/47
Menyisipkan Elemen Linked List

Diberikan node yang bukan tail, sisipkan newNode sesudahnya.

newNode
20
node
... 1 16 ...

14/47
Menyisipkan Elemen Linked List (lanj.)

1. Isi pointer newNode.prev untuk mengarah ke node.


2. Isi pointer newNode.next untuk mengarah ke node.next.

newNode
20
node
... 1 16 ...

15/47
Menyisipkan Elemen Linked List (lanj.)

3. Perbaiki pointer newNode.prev .next untuk mengarah ke


newNode.
4. Perbaiki pointer newNode.next.prev untuk mengarah ke
newNode.

newNode
20
node
... 1 16 ...

16/47
Menyisipkan Elemen Linked List (lanj.)

• Untuk menyisipkan elemen di paling awal atau paling akhir,


gunakan cara serupa.
• Tidak ada pergeseran, hanya ”cabut” dan ”pasang” pointer.
• Kompleksitasnya adalah O(1).

17/47
Menghapus Elemen Linked List

Diberikan node yang bukan head maupun tail, hapus dari linked
list.

node
... 1 20 16 ...

18/47
Menghapus Elemen Linked List (lanj.)

1. Ubah node.prev .next menjadi node.next.


2. Ubah node.next.prev menjadi node.prev .

node
... 1 20 16 ...

19/47
Menghapus Elemen Linked List (lanj.)

3. Hapus node.

node
... 1 16 ...

20/47
Menghapus Elemen Linked List (lanj.)

• Anda juga dapat menghapus head atau tail dengan cara


serupa.
• Sama seperti menyisipkan elemen, tidak ada operasi
pergeseran di sini.
• Kompleksitasnya adalah O(1).

21/47
Rangkuman

• Linked list memang tidak mendukung random access secara


efisien.
• Namun linked list mendukung operasi menyisipkan dan
menghapus jika diketahui node tempat
penyisipan/penghapusan dilakukan.

22/47
Mengenal Stack
• Stack dapat dimisalkan seperti tumpukan piring pada
umumnya.
• Jika terdapat piring baru yang ingin dimasukkan, maka piring
tersebut masuk dari paling atas.
• Jika sebuah piring akan diambil dari tumpukan, maka yang
diambil juga piring yang paling atas.

23/47
Mengenal Stack (lanj.)
• Struktur data stack menyimpan informasi dalam bentuk
tumpukan.
• Informasi yang baru dimasukkan ke paling atas tumpukan.
• Hanya informasi paling atas yang bisa diakses/dihapus pada
setiap waktunya.
• Oleh karena itu struktur data stack disebut memiliki sifat
LIFO (Last In First Out).

24/47
Operasi pada Stack

Stack memiliki operasi sebagai berikut:


• push, yaitu memasukkan elemen baru ke bagian atas
tumpukan.
• pop, yaitu membuang elemen paling atas tumpukan.
• top, yaitu mengakses elemen paling atas tumpukan.

25/47
Aplikasi Stack

• Eksekusi fungsi pada sejumlah bahasa pemrograman biasanya


menggunakan struktur data stack, tertama untuk fungsi
rekursif.
• Pemanggilan fungsi rekursif yang menambah kedalaman
berarti melakukan ”push” pada stack eksekusi fungsi.
• Fungsi yang dieksekusi adalah fungsi di paling atas stack.
• Setelah fungsi di paling atas selesai, dilakukan ”pop” dan
eksekusi fungsi dilanjutkan ke fungsi yang ada di paling atas
stack berikutnya.
• Oleh sebab itu, ketika pemanggilan fungsi terlalu dalam,
terjadi stack overflow.

26/47
Aplikasi Stack Lainnya

• Stack juga digunakan pada kalkulator ekspresi matematika


dalam notasi postfix.
• Notasi postfix adalah notasi penulisan ekspresi matematika
dengan urutan operand, operand, dan operator.
• Contoh:
• ”1 2 +” bermakna ”1 + 2”
• ”1 2 3 + −” bermakna ”1 − (2 + 3)”
• ”1 2 3 + − 4 ×” bermakna ”(1 − (2 + 3)) × 4”
• Notasi yang biasa kita gunakan adalah notasi infix, yaitu
dengan urutan operand, operator, dan operand.

27/47
Aplikasi Stack Lainnya (lanj.)

• Diberikan sebuah ekspresi dalam notasi postfix, nilai akhirnya


dapat dicari dengan skema kerja stack.
• Pada awalnya, inisialisasi sebuah stack kosong.
• Proses ekspresi dari kiri ke kanan:
1. Jika ditemukan operand, push ke dalam stack.
2. Jika ditemukan operator, pop dua kali untuk mendapat dua
operand teratas stack, hitung, lalu push kembali ke dalam
stack.
• Satu-satunya nilai terakhir di dalam stack adalah hasil
ekspresinya.

28/47
Eksekusi Ekspresi Postfix

Ekspresi: 1 2 3 + − 4 ×
1. Push angka 1, stack: [1].
2. Push angka 2, stack: [1, 2].
3. Push angka 3, stack: [1, 2, 3].
4. Ditemukan +:
• Pop dua kali, didapat nilai 2 dan 3, stack: [1].
• Operasikan 2 + 3, dan push, stack: [1, 5]
5. ...

29/47
Eksekusi Ekspresi Postfix (lanj.)

Ekspresi: 1 2 3 + − 4 ×
5. Ditemukan -:
• Pop dua kali, didapat nilai 1 dan 5, stack: [].
• Operasikan 1 - 5, dan push, stack: [-4]
6. Push angka 4, stack: [-4, 4].
7. Ditemukan ×:
• Pop dua kali, didapat nilai -4 dan 4, stack: [].
• Operasikan -4 × 4, dan push, stack: [-16]

Jadi 1 2 3 + − 4 × = −16

30/47
Implementasi Stack

• Anda dapat mengimplementasikan stack menggunakan singly


linked list.
• Node yang perlu Anda simpan cukup head saja (atau tail
saja), berhubung penyisipan/penghapusan selalu dilakukan di
bagian tersebut.

31/47
Alternatif Implementasi Stack

• Alternatif yang seringkali lebih mudah adalah menggunakan


sebuah array dan variabel penunjuk.
• Variabel penunjuk ini menyatakan indeks array yang menjadi
elemen paling atas stack, dan bergerak maju/mundur sesuai
dengan perintah push/pop.
• Seluruh operasi dapat dilakukan dalam O(1).

32/47
Alternatif Implementasi Stack (lanj.)
initializeStack(maxSize)
1 // Buat array stack berukuran maxSize
2 topOfStack = 0

push(item)
1 topOfStack = topOfStack + 1
2 stack[topOfStack] = item

pop()
1 topOfStack = topOfStack − 1

top()
1 return stack[topOfStack]

33/47
Alternatif Implementasi Stack (lanj.)

• Pastikan nilai maxSize sama dengan maksimal operasi push


yang mungkin dilakukan.
• Pada operasi pop, kita tidak benar-benar menghapus
elemennya, melainkan hanya variabel penunjuknya yang
”dimundurkan”.

34/47
Bagian 2

Stack

35/47
Contoh Soal

• Anda diberikan sebuah string, misalnya acaabcbcd.


• Cari string abc dalam string tersebut. Jika ditemukan maka
hapus string abc tersebut, lalu ulangi pencarian.
• Pencarian berakhir ketika tidak terdapat string abc lagi.
• Tentukan total penghapusan yang berhasil dilakukan
• Contoh, pada string acaabcbcd terdapat sebuah string abc,
dan hapus string tersebut menjadi acabcd. Lalu, ditemukan
lagi string abc dan hapus menjadi acd. Karena tidak
ditemukan lagi string abc, maka jawabannya adalah 2.

36/47
Pembahasan Soal

• Lakukan iterasi setiap karakter pada string tersebut.


• Untuk setiap karakter, push ke dalam stack.
• Cek 3 karakter teratas pada stack.
• Jika 3 karakter teratas merupakan abc, artinya terdapat 1
penghapusan. Lalu pop ketiga huruf tersebut dari stack.
Pada soal ini, Anda harus dapat memodifikasi struktur data stack
agar Anda dapat melakukan operasi top pada 3 elemen teratas.
Kompleksitas total adalah O(N), dengan N merupakan panjang
string.

37/47
Bagian 3

Queue

38/47
Mengenal Queue

• Apakah anda pernah melihat antrean pembelian?


• Struktur data queue mirip dengan analogi antrean tersebut.
• Saat seorang ingin masuk ke antrean, maka orang tersebut
harus mengantri dari belakang.
• Sementara itu, orang yang dilayani terlebih dahulu adalah
orang yang paling depan.

39/47
Mengenal Queue (lanj.)
• Struktur data queue menyimpan informasi dalam bentuk
antrean.
• Informasi yang baru dimasukkan ke paling belakang antrean.
• Hanya informasi paling depan yang bisa diakses/dihapus pada
setiap waktunya.
• Oleh karena itu struktur data queue disebut memiliki sifat
FIFO (First In First Out).

40/47
Operasi Queue

Queue memiliki beberapa operasi yang dapat dilakukan:


• push, yaitu memasukkan elemen baru ke bagian akhir antrean.
• pop, yaitu mengeluarkan elemen paling depan antrean.
• front, yaitu mengakses elemen yang paling depan antrean.

41/47
Aplikasi Queue

• Sesuai namanya, pada komputer queue digunakan untuk


berbagai hal yang memerlukan antrean.
• Misalnya antrean berkas-berkas yang akan diunduh dari
internet untuk ditampilkan pada browser Anda.
• Queue akan sering kita gunakan ketika sudah memasuki
materi graf.
• Tepatnya ketika melakukan breadth-first search.

42/47
Implementasi Queue

• Anda dapat mengimplementasikan queue menggunakan singly


linked list.
• Anda dapat menyimpan node head dan tail.
• Setiap push, sisipkan elemen sesudah tail.
• Setiap pop, hapus node head.

43/47
Alternatif Implementasi Queue

• Lagi-lagi, alternatif yang seringkali lebih mudah adalah


menggunakan sebuah array dan dua variabel penunjuk.
• Variabel penunjuk ini menyatakan indeks array yang menjadi
elemen paling depan dan belakang queue.
• Kedua variabel penunjuk ini selalu bergerak maju.
• Seluruh operasi dapat dilakukan dalam O(1).

44/47
Alternatif Implementasi Queue (lanj.)
initializeQueue(maxSize)
1 // Buat array queue berukuran maxSize
2 head = 1
3 tail = 0
push(item)
1 tail = tail + 1
2 queue[tail] = item
pop()
1 head = head + 1

front()
1 return queue[head]

45/47
Alternatif Implementasi Queue (lanj.)

• Kelemahan dari implementasi ini adalah beberapa elemen di


bagian awal array tidak digunakan kembali.
• Misalnya telah dilakukan 15 kali push, dan 11 kali pop.
• Sebanyak 11 elemen pertama pada array tidak akan
digunakan kembali.
• Ini adalah pemborosan, karena aslinya hanya terdapat 4
elemen di dalam queue.
• Meskipun demikian, dalam dunia kompetisi hal ini masih
dapat diterima.
• Pastikan nilai maxSize sama dengan maksimal operasi push
yang mungkin dilakukan.

46/47
Penutup

• Struktur data yang baru kita pelajari ini merupakan struktur


data dasar.
• Pada lain kesempatan, kita akan mempelajari struktur data
yang lebih kompleks dan manfaatnya lebih ”berasa”, seperti
heap untuk priority queue, binary search tree untuk kamus,
dan segment tree untuk dynamic range query.

47/47
Perkenalan Graf

Tim Olimpiade Komputer Indonesia

1/47
Pendahuluan

Melalui dokumen ini, kalian akan:


• Mengenal konsep dan terminologi graf.
• Mengetahui jenis-jenis graf.
• Mengenal representasi graf pada pemrograman.
• Mengenal metode-metode yang digunakan dalam graf.

2/47
Motivasi

• Diberikan sebuah struktur kota dan jalan.


• Terdapat V kota, dan E ruas jalan.
• Setiap ruas jalan menghubungkan dua kota.
• Diberikan kota awal, tentukan berapa banyak ruas jalan paling
sedikit yang perlu dilalui untuk mencapai suatu kota tujuan!

3/47
Pertanyaan

Bagaimana cara merepresentasikan struktur perkotaan dan


jalan pada pemrograman?

4/47
Bagian 1

Perkenalan Graf

5/47
Mengenal Graf

Graf adalah struktur yang terdiri dari node/vertex dan edge.

Node direpresentasikan dengan bentuk lingkaran dan edge


direpresentasikan dengan bentuk garis pada ilustrasi berikut:

E B

D C

6/47
Mengenal Graf (lanj.)
• Edge merupakan penghubung antar node.
• Degree suatu node merupakan jumlah edge yang terhubung
pada node tersebut
• Pada contoh ilustrasi berikut, degree node A = 4, degree
node B = 3, dan degree node C = 5.

E B

D C

7/47
Jenis Graf
Berdasarkan hubungan antar node:
• Graf tak berarah: edge dari A ke B dapat ditelusuri dari A
ke B dan B ke A.
• Graf berarah: edge dari A ke B hanya dapat ditelusuri dari
dari A ke B.

A A
B D D
B
C C
Graf tak berarah dan graf berarah

8/47
Jenis Graf (lanj.)
Berdasarkan bobot dari edge:
• Graf tak berbobot, yaitu graf dengan edge yang bobotnya
seragam dan hanya bermakna terdapat hubungan antar node.
• Graf berbobot, yaitu graf dengan edge yang dapat memiliki
bobot berbeda-beda. Bobot pada edge ini bisa jadi berupa
biaya, jarak, atau waktu yang harus ditempuh jika
menggunakan edge tersebut.

A 1
A

B B 5
D 3
D
2
C C
Graf tak berbobot dan graf berbobot

9/47
Jenis Graf (lanj.)
Tentu saja, suatu graf dapat memiliki kombinasi dari sifat-sifat
tersebut.
Misalnya graf berbobot berarah:

1 A
3 5
B 2 D
8
C

10/47
Representasi Graf pada Pemrograman

• Dalam pemrograman, dibutuhkan sebuah struktur agar data


mengenai graf dapat disimpan dan diolah.
• Representasi yang akan kita pelajari adalah adjacency matrix,
adjacency list, dan edge list.
• Masing-masing representasi memiliki keuntungan dan
kerugiannya.
• Penggunaan representasi graf bergantung dengan masalah
yang sedang dihadapi.

11/47
Adjacency Matrix

• Kita akan menggunakan matriks dengan ukuran N × N


dengan N merupakan banyaknya node.
• Pada graf tidak berbobot:
• Jika terdapat edge dari A ke B, maka matrix[A][B] = 1.
• Jika tidak ada, maka matrix[A][B] = 0.

A B C D A
A 0 1 1 1
B 1 0 0 0 D
C 0 0 0 1 B
D 0 0 0 0 C

12/47
Adjacency Matrix (lanj.)

• Pada graf berbobot:


• Jika terdapat edge dari A ke B dengan bobot w, maka
matrix[A][B] = w .
• Jika tidak ada, maka dapat ditulis matrix[A][B] = ∞.

A B C D A
A ∞ 3 2 5 1
3 5
B 1 ∞ ∞ ∞ 2 D
C ∞ ∞ ∞ 8 B 8
D ∞ ∞ ∞ ∞ C

13/47
Analisis Adjacency Matrix

• Pada graf tak berarah, adjacency matrix simetris terhadap


diagonalnya.
• Representasi ini mudah diimplementasikan.
• Menambah atau menghapus edge dapat dilakukan dalam
O(1).
• Untuk memeriksa apakah dua node terhubung juga dapat
dilakukan dalam O(1).
• Untuk mendapatkan daftar tetangga dari suatu node, dapat
dilakukan iterasi O(V ), dengan V adalah banyaknya node.

14/47
Kekurangan Adjacency Matrix

• Kekurangan dari representasi ini adalah boros memori.


• Memori yang dibutuhkan selalu O(V 2 ), tidak dapat digunakan
untuk graf dengan node mencapai ratusan ribu.
• Jika banyaknya edge jauh lebih sedikit dari O(V 2 ), maka
banyak memori yang terbuang.

15/47
Adjacency List

• Merupakan salah satu alternatif representasi graf.


• Untuk setiap node, buat sebuah list yang berisi keterangan
mengenai tetangga node tersebut.
• Misalnya untuk graf tak berbobot, kita cukup menyimpan
node-node tetangga untuk setiap node.

A [B, C , D] A
B [A]
C [D] D
D [] B
C

16/47
Adjacency List (lanj.)

• Untuk graf berbobot, kita dapat menyimpan node-node


tetangga beserta bobotnya.

1 A
A [< B, 3 >, < C , 2 >, < D, 5 >]
B [< A, 1 >] 3 5
B 2 D
C [< D, 8 >] 8
D [] C

17/47
Implementasi Adjacency List

• Kita dapat menggunakan struktur data array of lists.


• Tiap list berisi keterangan mengenai tetangga suatu node.
• Ukuran dari array merupakan V , yang mana V merupakan
banyaknya node.
• Dengan menggunakan list, banyaknya memori yang digunakan
untuk setiap node hanya sebatas banyak tetangganya.
• Secara keseluruhan jika graf memiliki E edge, maka total
memori yang dibutuhkan adalah O(E ).

18/47
Implementasi Adjacency List (lanj.)

• List yang dimaksud bisa berupa linked list atau resizable array.
• Bagi pengguna C++ atau Java, struktur list yang dapat
digunakan adalah vector atau ArrayList.
• Untuk pengguna C atau Pascal, struktur linked list perlu
dibuat terlebih dahulu.

19/47
Analisis Adjacency List

• Kompleksitas menambah edge adalah O(1), dan menghapus


adalah O(K ) dengan K adalah banyaknya tetangga dari node
yang edge-nya dihapus.
• Memeriksa apakah dua node terhubung oleh edge juga
dilakukan dalam O(K ).
• Demikian juga untuk mendapatkan daftar tetangga dari node,
kompleksitasnya adalah O(K ). Perhatikan bahwa pencarian
daftar tetangga ini sudah paling efisien.

20/47
Edge List

• Sesuai namanya, kita merepresentasikan graf dengan sebuah


list.
• Seluruh keterangan edge dimasukkan kedalam list tersebut.
• Berbeda dengan adjacency list yang membutuhkan array of
list, representasi ini hanya butuh sebuah list.

< A, B >, A
< A, C >,
< A, D >, D
< B, A >, B
< C, D > C

21/47
Edge List (lanj.)

• Untuk graf berbobot, kita juga menyimpan bobot dari setiap


edge.

< A, B, 3 >, A
< A, C , 2 >, 1
3 5
< A, D, 5 >, 2 D
< B, A, 1 >, B 8
< C , D, 8 > C

22/47
Implementasi Edge List

• Untuk implementasinya, kita membutuhkan struktur data


sebuah list (atau array ).
• Jelas bahwa memori yang dibutuhkan adalah O(E ), dengan E
adalah banyaknya edge pada keseluruhan graf.

• Pada beberapa kasus, dilakukan pengurutan terhadap edge list


atau digunakan struktur data binary search tree, yang
memungkinkan implementasinya lebih efisien. Namun untuk
saat ini kita tidak mempelajari hal tersebut.

23/47
Analisis Edge List

• Kompleksitas menambah edge adalah O(1).


• Bergantung dari implementasi, kompleksitas menghapus edge
dan memeriksa keterhubungan sepasang node bisa berupa
O(log E ) sampai O(E ).
• Demikian juga untuk mendapatkan daftar tetangga dari node,
kompleksitasnya bisa berkisar antara O(log E + K ) sampai
O(E ), dengan K adalah banyaknya tetangga dari node
tersebut.

24/47
Keuntungan dan Kerugian Representasi Graf
Untuk graf dengan V node dan E edge:

Adj.Matrix Adj.List Edge List

Tambah edge O(1) O(1) O(1)


Hapus edge O(1) O(K ) O(E )
Cek keterhubungan O(1) O(K ) O(E )
Daftar tetangga O(V ) O(K ) O(E )
Kebutuhan memori O(V 2 ) O(E ) O(E )

Dengan K adalah banyaknya node yang bertetangga dengan node


yang sedang kita periksa.

25/47
Bagian 2

Penjelajahan Graf

26/47
Penjelajahan Graf

• Representasi graf saja belum berguna karena belum dapat


mencari informasi mengenai suatu graf
• Penjelajahan graf merupakan penelusuran node-node pada
suatu graf.

27/47
Penjelajahan Graf (lanj.)

• Diberikan node A dan node B, apakah dari node A kita dapat


pergi ke node B dengan edge yang ada?
• Permasalahan tersebut dapat diselesaikan menggunakan
penjelajahan graf.

• Terdapat 2 metode yang dapat digunakan, yaitu DFS dan


BFS.

28/47
DFS: Depth-First Search
Penelusuran dilakukan terhadap node yang lebih dalam terlebih
dahulu (depth-first).
Sebagai contoh, misal terdapat graf berikut:

29/47
DFS: Depth-First Search (lanj.)
Penelusuran secara DFS akan dilakukan dengan cara berikut:

2 6 7

3 4 8 11

5 9 10

• Angka pada node menunjukkan urutan node tersebut


dikunjungi.
• Node 1 dikunjungi pertama, node 2 dikunjungi kedua, dan
seterusnya).

30/47
Penelusuran DFS
1

2 6 7

3 4 8 11

5 9 10

• Dapat dilihat bahwa DFS mencoba menelusuri node yang


dalam terlebih dahulu.
• Node yang dekat dengan node pertama (seperti node 7 dan
8) akan dikunjungi setelah DFS selesai mengunjungi node
yang lebih dalam (seperti node 3, 4, dan 5)
• Dalam pemrograman, DFS biasa dilakukan dengan rekursi
atau struktur data stack.
31/47
Implementasi DFS (Rekursif)
Asumsikan:
• Setiap node dinomori dari 1 sampai V
• adj(x) menyatakan himpunan tetangga dari node x.
• visited[x] bernilai true hanya jika x telah dikunjungi.

DFS(curNode)
1 print ”mengunjungi curNode”
2 visited[curNode] = true
3 for each adjNode ∈ adj(curNode)
4 if not visited[adjNode]
5 DFS(adjNode)

32/47
Implementasi DFS (Stack)

DFS()
1 // Inisialisasi stack sebagai stack kosong.
2 stack.push(initialNode)
3 visited[initialNode] = true
4 while not stack.empty ()
5 curNode = stack.top()
6 stack.pop()
7 print ”mengunjungi curNode”
8 visited[adjNode] = true
9 for each adjNode ∈ adj(curNode)
10 if not visited[adjNode]
11 stack.push(adjNode)

33/47
BFS: Breadth-First Search
Penelusuran node pada graf dilakukan lapis demi lapis.
Semakin dekat suatu node dengan node awal, node tersebut akan
dikunjungi terlebih dahulu.

2 3 4

5 6 7 8

9 10 11

34/47
Penelusuran BFS

2 3 4

5 6 7 8

9 10 11

• Angka pada gambar menunjukkan urutan node tersebut


dikunjungi.
• Dalam pemrograman, BFS biasa diimplementasikan dengan
bantuan struktur data queue.

35/47
Implementasi BFS

BFS()
1 // Inisialisasi queue sebagai queue kosong.
2 queue.push(initialNode)
3 visited[initialNode] = true
4 while not queue.empty ()
5 curNode = queue.front()
6 queue.pop()
7 print ”mengunjungi curNode”
8 for each adjNode ∈ adj(curNode)
9 if not visited[adjNode]
10 visited[adjNode] = true
11 queue.push(adjNode)

36/47
Analisis Kompleksitas

• Baik DFS maupun BFS sama-sama mengunjungi setiap node


tepat satu kali, dengan memanfaatkan seluruh edge.
• Kompleksitas dari kedua metode adalah:
• O(V 2 ), jika digunakan adjacency matrix.
• O(V + E ), jika digunakan adjacency list.

• Penggunaan DFS atau BFS dapat disesuaikan dengan


persoalan yang dihadapi.

37/47
Contoh Permasalahan (lanj.)

Pak Dengklek tinggal di kota A. Suatu hari, beliau ingin pergi ke


kota B. Terdapat beberapa ruas jalan yang menghubungkan
kota-kota dalam negara tempat beliau tinggal. Namun karena
sudah tua, Pak Dengklek ingin melewati sesedikit mungkin ruas
jalan untuk sampai ke kota B.

Diberikan informasi mengenai struktur kota dan ruas jalan,


tentukan berapa banyak ruas jalan yang perlu beliau lewati untuk
pergi dari kota A ke kota B!

38/47
Solusi

• Permasalahan ini dapat diselesaikan dengan BFS.


• Karena sifat BFS yang menelusuri node lapis demi lapis, maka
dapat disimpulkan jika suatu node dikunjungi, maka jarak
yang ditempuh dari awal sampai node tersebut pasti jarak
terpendek.
• Hal ini selalu benar untuk segala graf tak berbobot, BFS
selalu menemukan shortest path dari suatu node ke seluruh
node lainnya.

39/47
Implementasi Solusi
shortestPath(A, B)
1 // Inisialisasi queue sebagai queue kosong.
2 // Inisialisasi array visitTime dengan -1.
3 queue.push(A)
4 visitTime[A] = 0
5 while not queue.empty ()
6 curNode = queue.front()
7 queue.pop()
8 for each adjNode ∈ adj(curNode)
9 // Jika adjNode belum pernah dikunjungi...
10 if visitTime[adjNode] == −1
11 visitTime[adjNode] = visitTime[curNode] + 1
12 queue.push(adjNode)
13 return visitTime[B]

40/47
Bagian 3

Macam-Macam Graf

41/47
Macam-Macam Graf

• Terdapat Graf yang memiliki suatu karakteristik khusus,


sehingga penyelesaian masalah yang melibatkan graf ini dapat
memanfaatkan karakter tersebut.
• Contoh macam-macam graf adalah tree, directed acyclic
graph, dan bipartite graph.
• Kali ini kita akan menyinggung tree dan directed acyclic
graph.

42/47
Tree
• Tree merupakan bentuk khusus dari graf.
• Seluruh node pada tree terhubung (tidak ada node yang tidak
dapat dikunjungi dari node lain) dan tidak terdapat cycle.
• Banyaknya edge dalam sebuah tree pasti V − 1, dengan V
adalah banyaknya node.

43/47
Contoh Tree

F F
B G
E A
B A D I
D C E H
C

Gambar kiri bukan tree karena memiliki cycle, sedangkan gambar


kanan merupakan tree.

44/47
Directed Acyclic Graph
• directed acyclic graph (DAG) merupakan bentuk khusus dari
directed graf.
• DAG tidak memiliki cycle.
• Berbeda dengan tree yang mana setiap node harus dapat
dikunjungi dari node lainnya, sifat tersebut tidak berlaku pada
DAG.

F
B G
A D
C
E

45/47
Contoh Directed Acyclic Graph

F
B G C
A D A B E
C
E D F

Pada gambar di atas, gambar kiri merupakan DAG, sedangkan


gambar kanan bukan DAG karena memiliki cycle.

46/47
Penutup

• Hampir setiap kompetisi pasti memiliki soal yang bertemakan


graf.
• Mampu menguasai dasar penyelesaian masalah graf menjadi
kemampuan penting dalam dunia kompetisi.

47/47
Struktur Data NonLinear:
Disjoint Set

Tim Olimpiade Komputer Indonesia

1/25
Pendahuluan

Melalui dokumen ini, kalian akan:


• Mengenal dan mengimplementasikan struktur data disjoint
set.
• Mengetahui mengapa diperlukan disjoint set.

2/25
Motivasi

Terdapat N orang di suatu ruangan, dinomori dari 1 sampai


dengan N. Secara bertahap, mereka membentuk suatu kelompok.

Anda diberikan sejumlah operasi. Setiap operasi dapat berbentuk


salah satu dari:
• join(a, b), artinya kelompok orang ke-a dan kelompok orang
ke-b bergabung.
• check(a, b), artinya periksa apakah orang ke-a dan orang ke-b
sedang berada di kelompok yang sama.

3/25
Contoh Perilaku

Dengan N = 5, Berikut contoh operasinya dan perilaku yang


diharapkan:
• Pada kondisi awal, setiap orang dapat dianggap membentuk
kelompok sendiri [1], [2], [3], [4], [5].
• join(1, 4), kini kelompok yang ada adalah [1, 4], [2], [3], [5].
• check(1, 2): laporkan bahwa 1 dan 2 berada di kelompok
berbeda.
• join(1, 2), kini kelompok yang ada adalah [1, 2, 4], [3], [5].
• ...

4/25
Contoh Perilaku (lanj.)

• ...
• check(1, 2): laporkan bahwa 1 dan 2 berada di kelompok
yang sama.
• join(3, 5), kini kelompok yang ada adalah [1, 2, 4], [3, 5].
• join(2, 3), kini kelompok yang ada adalah [1, 2, 3, 4, 5].
• check(1, 5): laporkan bahwa 1 dan 5 berada di kelompok
yang sama.

5/25
Solusi Sederhana

• Dengan konsep graf, representasikan setiap orang sebagai


node.
• Setiap operasi join dapat dianggap dengan penambahan edge.
• Untuk operasi check, diperlukan penjelajahan graf untuk
memeriksa apakah kedua node terhubung.
• Representasi graf yang paling efisien untuk keperluan ini
adalah adjacency list.

6/25
Analisis Solusi Sederhana

Operasi Solusi Sederhana


join(a, b) O(1)
check(a, b) O(N) s.d. O(N 2 )

Kompleksitas check mencapai O(N 2 ) apabila penjelajahan graf


dilakukan secara naif dan seluruh kemungkinan edge dilalui.

7/25
Masalah Solusi Sederhana

• Solusi sederhana ini jelas tidak efisien ketika banyak dilakukan


operasi check(a, b).
• Kita akan mempelajari bagaimana disjoint set mengatasi
masalah ini secara efisien.

8/25
Disjoint Set

• Disjoint set merupakan struktur data yang efisien untuk


mengelompokkan elemen-elemen secara bertahap.
• Operasi yang didukung adalah:
• join, yaitu menggabungkan kelompok dari sepasang elemen.
• check, yaitu memeriksa apakah sepasang elemen berada di
kelompok yang sama.

9/25
Ide Dasar

• Untuk setiap kelompok yang ada, pilih suatu elemen sebagai


”perwakilan kelompok”.
• Setiap elemen perlu mengetahui siapa perwakilan
kelompoknya.
• Untuk memeriksa apakah dua elemen berada pada kelompok
yang sama, periksa apakah perwakilan kelompok mereka
sama.
• Untuk menggabungkan kelompok dari dua elemen, salah satu
perwakilan kelompok elemen perlu diwakilkan oleh
perwakilan kelompok elemen lainnya.

10/25
Cara Kerja Disjoint Set
• Setiap elemen perlu menyimpan pointer ke elemen yang
merupakan perwakilannya.
• Pointer yang ditunjuk oleh suatu perwakilan kelompok adalah
dirinya sendiri.

11/25
Cara Kerja Disjoint Set (lanj.)

• Karena pada awalnya setiap elemen membentuk kelompoknya


sendiri, maka awalnya setiap pointer ini menunjuk pada
dirinya sendiri.
• Untuk mempermudah, mari kita sebut pointer ini sebagai
parent.

12/25
Inisialisasi Disjoint Set

Berikut prosedur inisialisasi disjoint set untuk N elemen.

Asumsikan array par menyimpan indeks elemen yang ditunjuk


sebagai parent dari suatu elemen.

initialize()
1 for i = 0 to N − 1 // Indeks elemen dimulai dari 0 (zero-based)
2 par [i] = i

Cukup sederhana, yaitu setiap elemen menunjuk ke dirinya sendiri.

13/25
Operasi Join

• Ketika kelompok dua elemen perlu digabungkan, ubah parent


dari salah satu perwakilan kelompok ke kelompok lainnya.

join

14/25
Operasi Join (lanj.)
• Perhatikan bahwa yang perlu diubah adalah parent dari
perwakilan kelompok suatu elemen, bukan elemen itu sendiri.

join

join

15/25
Operasi Join (Implementasi)

Secara sederhana, operasi join dapat dituliskan dalam prosedur:

join(a, b)
1 repA = findRepresentative(a)
2 repB = findRepresentative(b)
3 par [repA] = repB

Fungsi findRepresentative(x) mengembalikan elemen


perwakilan dari kelompok tempat elemen x berada.

16/25
Operasi Join (implementasi findRepresentative)

Fungsi findRepresentative(x) dapat diimplementasikan secara


rekursif, yaitu sampai ditemukan elemen yang memiliki parent
berupa dirinya sendiri.

findRepresentative(x)
1 if par [x] == x
2 return x
3 else
4 return findRepresentative(par [x])

17/25
Kekurangan findRepresentative
• Fungsi findRepresentative memiliki kompleksitas sebesar
O(L), dengan L adalah panjangnya jalur dari elemen x sampai
elemen perwakilan kelompoknya.
• Ketika L mendekati N, fungsi ini tidak efisien bila dipanggil
berkali-kali.

x
18/25
Memperbaiki findRepresentative
• Kita dapat menerapkan teknik path compression, yaitu
mengubah nilai parent dari setiap elemen yang dilalui
langsung ke elemen perwakilan kelompok.
• Hal ini menjamin untuk pemanggilan findRepresentative
berikutnya pada elemen yang bersangkutan bekerja secara
lebih efisien.

x
19/25
Implementasi Path Compression

Tambahkan pencatatan elemen perwakilan kelompok untuk setiap


elemen yang dilalui.

findRepresentative(x)
1 if par [x] == x
2 return x
3 else
4 // Catat elemen representatifnya
5 par [x] = findRepresentative(par [x])
6 return par [x]

20/25
Analisis Kompleksitas findRepresentative
• Untuk disjoint set dengan N elemen, paling banyak terdapat
N parent yang dikenakan path compression.
• Apabila seluruh parent elemen sudah dikenakan path
compression, maka setiap elemen langsung menunjuk ke
elemen perwakilan kelompoknya.
• Artinya, kini fungsi findRepresentative bekerja dalam
O(1).

21/25
Analisis Kompleksitas findRepresentative (lanj.)

• Kompleksitas satu kali pemanggilan findRepresentative


tidak dapat didefinisikan secara pasti.
• Yang pasti adalah, kompleksitas total untuk pemanggilan
findRepresentative secara berkali-kali tidak akan lebih
dari O(N); sebab lebih dari itu dipastikan seluruh parent
sudah terkompresi secara merata.
• Setelah seluruh parent terkompresi secara merata,
kompleksitasnya adalah O(1).

22/25
Analisis Kompleksitas Join

• Kembali ke operasi join, kompleksitasnya bergantung pada


findRepresentative.
• Dapat dikatakan kompleksitas operasi join sama dengan
kompleksitas findRepresentative.

23/25
Operasi Check

• Untuk operasi check, cukup periksa apakah elemen perwakilan


kelompok kedua elemen sama.
• Lagi-lagi, kompleksitas operasi check sama dengan sama
dengan kompleksitas findRepresentative.

check(a, b)
1 return findRepresentative(a) == findRepresentative(b)

24/25
Penutup

• Disjoint set merupakan struktur data yang sederhana dan


mudah diimplementasikan.
• Biasanya struktur data ini dipakai untuk membantu
implementasi algoritma lainnya, seperti Minimum Spanning
Tree Kruskal.
• Setiap Anda mengingat operasi ”gabung” dan ”periksa”,
ingatlah disjoint set.

25/25
Struktur Data Nonlinear:
Heap

Tim Olimpiade Komputer Indonesia

1/64
Pendahuluan

Melalui dokumen ini, kalian akan:


• Mengenal dan mengimplementasikan struktur data heap.
• Mengetahui mengapa diperlukan heap.

2/64
Motivasi

Anda diberikan sejumlah operasi. Setiap operasi dapat berbentuk


salah satu dari:
• add(x), artinya simpan bilangan x.
• getMax(), artinya dapatkan bilangan terbesar yang saat ini
masih disimpan.
• deleteMax(), artinya hapus bilangan terbesar dari
penyimpanan.

3/64
Motivasi

Berikut contoh operasinya dan perilaku yang diharapkan:


• add(5), bilangan yang disimpan: [5].
• add(7), bilangan yang disimpan: [5, 7].
• add(3), bilangan yang disimpan: [5, 7, 3].
• getMax(), laporkan bahwa 7 merupakan bilangan terbesar.
• deleteMax(), bilangan yang disimpan: [5, 3].
• getMax(), laporkan bahwa 5 merupakan bilangan terbesar.

4/64
Solusi Sederhana

• Solusi paling mudah adalah membuat sebuah array besar dan


variabel yang menunjukkan posisi terakhir elemen pada array .
• Untuk setiap operasi add(x), tambahkan elemen array , geser
variabel penunjuk, lalu urutkan data.
• Operasi getMax() dapat dilayani dengan mengembalikan
elemen terbesar.
• Operasi deleteMax() dapat dilayani dengan menggeser
variabel penunjuk.

5/64
Analisis Solusi Sederhana

• Misalkan N menyatakan banyaknya elemen pada array .


• Dengan cara ini, operasi add(x) berlangsung dalam
O(N log N), apabila pengurutannya menggunakan quicksort.
• Operasi getMax() dan deleteMax() berlangsung dalam O(1).

• Perhatikan bahwa pengurutan akan lebih efisien jika digunakan


insertion sort, sehingga kompleksitas add(x) menjadi O(N).

6/64
Analisis Solusi Sederhana

Operasi Dengan sorting


add(x) O(N log N)
getMax() O(1)
deleteMax() O(1)

7/64
Masalah Solusi Sederhana

• Solusi sederhana ini tidak efisien ketika banyak dilakukan


operasi add(x).
• Kita akan mempelajari bagaimana heap mengatasi masalah ini
secara efisien.

8/64
Bagian 1

Pengenalan Heap

9/64
Heap

• Heap merupakan struktur data yang umum dikenal pada ilmu


komputer.
• Nama heap sendiri berasal dari Bahasa Inggris, yang berarti
”gundukan”.

10/64
Operasi Heap

Heap mendukung operasi:


• push, yaitu memasukan elemen baru ke penyimpanan.
• pop, yaitu membuang elemen terbesar dari penyimpanan.
• top, yaitu mengakses elemen terbesar dari penyimpanan.

11/64
Cara Kerja Heap

• Heap dapat diimplementasikan dengan berbagai cara.


• Kita akan mempelajari salah satunya, yaitu binary heap.
• Sebelum itu, diperlukan pengetahuan mengenai tree.

12/64
Tree
• Seperti yang telah dipelajari, tree merupakan suatu graf yang
setiap node-nya saling terhubung dan tidak memiliki cycle.

13/64
Rooted Tree
• Suatu tree yang memiliki hierarki dan memiliki sebuah akar
disebut sebagai rooted tree.

14/64
Binary Tree
• Suatu rooted tree yang setiap node-nya memiliki 0, 1, atau 2
anak disebut dengan binary tree.

15/64
Full Binary Tree
• Suatu binary tree yang seluruh node-nya memiliki 2 anak,
kecuali tingkat paling bawah yang tidak memiliki anak,
disebut dengan full binary Tree
• Bila banyaknya node adalah N, maka ketinggiannya adalah
O(log N).

16/64
Complete Binary Tree
Complete binary tree adalah binary tree yang:
• Seluruh node-nya memiliki 2 anak, kecuali tingkat paling
bawah.
• Tingkat paling bawahnya dapat terisi sebagian, tetapi harus
terisi dari kiri ke kanan.

• Bila banyaknya node adalah N, maka ketinggiannya


adalah O(log N). 17/64
Bukan Complete Binary Tree
Berikut bukan complete binary tree, sebab elemen di tingkat paling
bawah tidak berisi dari kiri ke kanan (terdapat lubang).

18/64
Bukan Complete Binary Tree
Berikut juga bukan complete binary tree, sebab terdapat node
tanpa 2 anak pada tingkat bukan paling bawah.

19/64
Struktur Binary Heap

Struktur data binary heap memiliki sifat:


• Berstruktur complete binary tree.
• Setiap node merepresentasikan elemen yang disimpan pada
heap.
• Setiap node memiliki nilai yang lebih besar daripada node
anak-anaknya.

20/64
Contoh Binary Heap

7 7

5 4 6 2
1 3 4

21/64
Contoh Bukan Binary Heap
Bukan binary heap.

7 7

5 9 7 2
1 3 4

22/64
Mengapa Harus Demikian?

• Struktur seperti ini menjamin operasi-operasi yang dilayani


heap dapat dilakukan secara efisien.
• Misalkan N adalah banyaknya elemen yang sedang disimpan.
• Operasi push dan pop bekerja dalam O(log N), sementara top
bekerja dalam O(1).
• Kita akan melihat satu persatu bagaimana operasi tersebut
dilaksanakan.

23/64
Operasi Push

Melakukan push pada binary heap dilakukan dengan 2 tahap:


• Tambahkan node baru di posisi yang memenuhi aturan
complete binary tree.
• Selama elemen node yang merupakan orang tua langsung dari
elemen ini memiliki nilai yang lebih kecil, tukar nilai elemen
kedua node tersebut.

24/64
Operasi Push
Sebagai contoh, misalkan hendak ditambahkan elemen bernilai 8
ke suatu binary heap berikut:

7 7

5 4 6 2
1 3 4

25/64
Operasi Push (lanj.)
Tambahkan node.

7 7

5 4 6 2
1 3 4 8

26/64
Operasi Push (lanj.)
Karena parent-nya memiliki nilai lebih kecil, tukar nilainya.

7 7

5 8 6 2
1 3 4 4

27/64
Operasi Push (lanj.)
Karena parent-nya masih memiliki nilai lebih kecil, tukar lagi.

8 7

5 7 6 2
1 3 4 4

28/64
Operasi Push (lanj.)
Parent-nya sudah memiliki nilai yang lebih besar.
Operasi push selesai.

8 7

5 7 6 2
1 3 4 4

29/64
Kompleksitas Push

• Kasus terburuk terjadi ketika pertukaran yang terjadi paling


banyak.
• Hal ini terjadi ketika elemen yang dimasukkan merupakan nilai
yang paling besar pada heap.
• Banyaknya pertukaran yang terjadi sebanding dengan
kedalaman dari complete binary tree.
• Kompleksitas untuk operasi push adalah O(log N).

30/64
Operasi Pop

Melakukan pop pada binary heap dilakukan dengan 3 tahap:


• Tukar posisi elemen pada root dengan elemen terakhir
mengikuti aturan complete binary tree.
• Buang elemen terakhir binary heap, yang telah berisi elemen
dari root.
• Selama elemen yang ditukar ke posisi root memiliki anak
langsung yang berelemen lebih besar, tukar elemen tersebut
dengan salah anaknya yang memiliki elemen terbesar.

31/64
Operasi Pop (lanj.)
Misalkan akan dilakukan pop pada heap berikut:

8 7

5 7 6 2
1 3 4 4

32/64
Operasi Pop (lanj.)
Tukar elemen pada root dengan elemen terakhir pada complete
binary tree.

8 7

5 7 6 2
1 3 4 9

33/64
Operasi Pop (lanj.)
Buang elemen terakhir.

8 7

5 7 6 2
1 3 4 9

34/64
Operasi Pop (lanj.)
Perbaiki struktur heap dengan menukar elemen pada root dengan
anaknya yang bernilai terbesar.

4 7

5 7 6 2
1 3 4

35/64
Operasi Pop (lanj.)
Karena masih terdapat anaknya yang lebih besar, tukar lagi.

7 7

5 4 6 2
1 3 4

36/64
Operasi Pop (lanj.)
Kini sudah tidak ada anak yang bernilai lebih besar, operasi pop
selesai.

7 7

5 4 6 2
1 3 4

37/64
Kompleksitas Pop

• Kasus terburuk juga terjadi ketika pertukaran yang terjadi


paling banyak.
• Hal ini terjadi ketika elemen yang ditempatkan di root cukup
kecil, sehingga perlu ditukar sampai ke tingkat paling bawah.
• Banyaknya pertukaran yang terjadi sebanding dengan
kedalaman dari complete binary tree.
• Kompleksitas untuk operasi pop adalah O(log N).

38/64
Operasi Top

• Operasi ini sebenarnya sesederhana mengembalikan elemen


pada root binary heap.
• Kompleksitas operasi ini adalah O(1).

39/64
Analisis Solusi dengan Heap

Penerapan heap pada persoalan motivasi:

Operasi Dengan sorting Dengan Heap


add(x) O(N log N) O(log N)
getMax() O(1) O(1)
deleteMax() O(1) O(log N)

Kini seluruh operasi dapat dilakukan dengan efisien.

40/64
Bagian 2

Implementasi Binary Heap

41/64
Membuat Tree

• Representasi tree pada implementasi dapat menggunakan


teknik representasi graf yang telah dipelajari sebelumnya.
• Namun, untuk tree dengan kondisi tertentu, kita dapat
menggunakan representasi yang lebih sederhana.
• Terutama pada kasus ini, yang mana tree yang diperlukan
adalah complete binary tree.

42/64
Representasi Complete Binary Tree

• Kedengarannya kurang masuk akal, tetapi complete binary


tree dapat direpresentasikan dengan sebuah array .
• Misalkan array ini bersifat zero-based, yaitu dimulai dari
indeks 0.
• Elemen pada indeks ke-i menyatakan elemen pada node ke-i.
• Anak kiri dari node ke-i adalah node ke-(2i + 1).
• Anak kanan dari node ke-i adalah node ke-(2i + 2).

43/64
Representasi Complete Binary Tree (lanj.)
0

1 2
2.0+1 2.0+2

2.1+1
3 2.1+2
4 5 2.2+1

2.1+2
2.1+1

0 1 2 3 4 5
2.0+1
2.2+1
2.0+2

44/64
Representasi Complete Binary Tree (lanj.)

• Dengan logika yang serupa, orang tua dari node ke-i adalah
node ke-b i−1
2 c.
• Apabila Anda memutuskan untuk menggunakan one-based,
berarti rumusnya menjadi:
• Anak kiri: 2i.
• Anak kanan: 2i + 1.
• Orang tua: b 2i c
• Representasi ini sangat mempermudah implementasi binary
heap.

45/64
Representasi Array
• Karena panjang array dapat bertambah atau berkurang,
diperlukan array yang ukurannya dinamis.
• Pada contoh ini, kita akan menggunakan array berukuran
statis dan sebuah variabel yang menyatakan ukuran array saat
ini.
• Berikut prosedur untuk inisialisasi, dengan asumsi maxSize
menyatakan ukuran terbesar pada heap yang mungkin.

initializeHeap(maxSize)
1 // Buat array arr berukuran maxSize
2 size = 0

*arr dan size merupakan variabel global, yaitu array dan variabel
yang menyatakan ukurannya saat ini.

46/64
Implementasi Fungsi Pembantu
Buat juga beberapa fungsi yang akan membantu mempermudah
penulisan kode.

getParent(x)
1 return floor((x − 1)/2)

getLeft(x)
1 return 2x + 1

getRight(x)
1 return 2x + 2

47/64
Implementasi Push

push(val)
1 i = size
2 arr [i] = val
3 while (i > 0) ∧ (arr [i] > arr [getParent(i)])
4 swap(arr [i], arr [getParent(i)])
5 i = getParent(i)
6 size = size + 1

48/64
Implementasi Pop
pop()
1 swap(arr [0], arr [size − 1])
2 size = size − 1
3 i =0
4 swapped = true
5 while swapped
6 maxIdx = i
7 if (getLeft(i) < size) ∧ (arr [maxIdx] < arr [getLeft(i)])
8 maxIdx = getLeft(i)
9 if (getRight(i) < size) ∧ (arr [maxIdx] < arr [getRight(i)])
10 maxIdx = getRight(i)
11 swap(arr [i], arr [maxIdx])
12 swapped = (maxIdx 6= i) // true bila terjadi pertukaran
13 i = maxIdx

49/64
Implementasi Top

top()
return arr [size − 1]

... sangat sederhana.

50/64
Pembuatan Heap

Ketika Anda memiliki data N elemen, dan hendak dimasukkan ke


dalam heap, Anda dapat:
1. Membuat heap kosong, lalu melakukan push satu per satu
hingga seluruh data dimuat heap. Kompleksitasnya
O(N log N).
2. Membuat array dengan N elemen, lalu array ini dibuat
menjadi heap dalam O(N). Caranya akan dijelaskan pada
bagian berikutnya.

51/64
Pembuatan Heap Secara Efisien

Untuk membuat heap dari N elemen, caranya adalah:


1. Buat array dengan N elemen, lalu isi array ini dengan
elemen-elemen yang akan dimasukkan pada heap.
2. Untuk semua node, mulai dari tingkat kedua dari paling
bawah:
• Misalkan node ini adalah node x.
• Pastikan subtree yang bermula pada node x membentuk
heap dengan operasi baru, yaitu heapify.

52/64
Operasi Heapify

• Heapify adalah operasi untuk membentuk sebuah heap yang


bermula pada suatu node, dengan asumsi anak kiri dan anak
kanan node tersebut sudah membentuk heap.

heap heap

53/64
Operasi Heapify (lanj.)

• Berhubung kedua anak telah membentuk heap, kita cukup


memindahkan elemen root ke posisi yang tepat.
• Jika elemen root sudah lebih besar daripada elemen anaknya,
tidak ada yang perlu dilakukan.
• Sementara bila elemen root lebih kecil daripada salah satu
elemen anaknya, tukar elemennya dengan elemen salah satu
anaknya yang paling besar.
• Kegiatan ini sebenarnya sangat mirip dengan yang kita
lakukan pada operasi pop.

54/64
Operasi Heapify (lanj.)

heapify(rootIdx)
1 i = rootIdx
2 swapped = true
3 while swapped
4 maxIdx = i
5 if (getLeft(i) < size) ∧ (arr [maxIdx] < arr [getLeft(i)])
6 maxIdx = getLeft(i)
7 if (getRight(i) < size) ∧ (arr [maxIdx] < arr [getRight(i)])
8 maxIdx = getRight(i)
9 swap(arr [i], arr [maxIdx])
10 swapped = (maxIdx 6= i) // true bila terjadi pertukaran
11 i = maxIdx

55/64
Operasi Heapify (lanj.)

Operasi pop sendiri dapat kita sederhanakan menjadi:

pop()
1 swap(arr [0], arr [size − 1])
2 size = size − 1
3 heapify(0)

56/64
Kompleksitas Heapify

• Sekali melakukan heapify, kasus terburuknya adalah elemen


root dipindahkan hingga ke paling bawah tree.
• Jadi kompleksitasnya adalah O(H), dengan H adalah
ketinggian dari complete binary tree.
• Berhubung H = O(log N), dengan N adalah banyaknya
elemen pada heap, kompleksitas heapify adalah O(log N).

57/64
Implementasi Pembangunan Heap Secara Efisien
• Setelah memahami heapify, kita dapat menulis prosedur
pembangunan heap dari array A berisi N elemen secara
efisien.
• Elemen terakhir yang berada pada tingkat kedua paling bawah
dapat ditemukan dengan mudah, yaitu elemen dengan indeks
N/2 dibulatkan ke bawah dan dikurangi 1.

makeHeap(N)
1 initializeHeap(N)
2 for i = 0 to N − 1
3 arr [size] = A[i]
4 size = size + 1
5 for i = bN/2c − 1 to 0
6 heapify(i)

58/64
Ilustrasi Pembangunan Heap Secara Efisien

Angka pada node menyatakan urutan pelaksanaan heapify.

4 3

2 1

59/64
Analisis Pembangunan Heap Secara Efisien

• Dilakukan N/2 kali operasi heapify, yang masing-masing


O(log N).
• Kompleksitasnya terkesan O(N log N).
• Namun pada kasus ini, sebenarnya setiap heapify tidak
benar-benar O(log N).
• Kenyataannya, banyak operasi heapify yang dilakukan pada
tingkat bawah, yang relatif lebih cepat dari heapify pada
tingkat di atas.
• Perhitungan secara matematis membuktikan bahwa
kompleksitas keseluruhan makeHeap adalah O(N).

60/64
Catatan Implementasi

• Tentu saja, Anda dapat membuat heap dengan urutan yang


terbalik, yaitu elemen terkecilnya di atas.
• Dengan demikian, operasi yang didukung adalah mencari atau
menghapus elemen terkecil.
• Biasanya heap dengan sifat ini disebut dengan min-heap,
sementara heap dengan elemen terbesar di atas disebut
dengan max-heap.
• Agar lebih rapi, Anda dapat menggunakan struct (C) atau
class (C++) pada implementasi heap.

61/64
Manfaat Heap

• Pada ilmu komputer, heap dapat digunakan sebagai priority


queue, yaitu antrean yang terurut menurut suatu kriteria.
• Sifat heap juga dapat digunakan untuk optimisasi suatu
algoritma. Contoh paling nyatanya adalah untuk
mempercepat algoritma Dijkstra.
• Berbagai solusi persoalan greedy juga dapat
diimplementasikan secara efisien dengan heap.

62/64
Library Heap

• Bagi pengguna C++, struktur data priority queue dari


header queue merupakan struktur data heap.

63/64
Penutup

• Dengan mempelajari heap, Anda memperdalam pemahaman


tentang bagaimana penggunaan struktur data yang tepat
dapat membantu menyelesaikan persoalan tertentu.
• Heap dapat digunakan untuk pengurutan yang efisien, dan
akan dibahas pada bagian berikutnya.

64/64
Aplikasi Struktur Data Nonlinear:
Heapsort

Tim Olimpiade Komputer Indonesia

1/8
Pendahuluan

Melalui dokumen ini, kalian akan:


• Menggunakan heap untuk heapsort.
• Mengamati bagaimana struktur data diaplikasikan dalam
penyelesaian masalah.

2/8
Kilas Balik

Ingat dengan selection sort?

Berikut adalah algoritmanya:


• Pilih elemen terkecil, tempatkan di paling awal data.
• Ulangi hingga seluruh data terurut menaik.

3/8
Kilas Balik (lanj.)

• Pencarian elemen terkecil pada selection sort dilakukan


dengan linear search.
• Sekarang kita telah mengetahui strategi pencarian elemen
terbesar/terkecil dari sekumpulan data secara efisien.
• Bagian pencarian elemen terkecil pada selection sort dapat
dioptimisasi menggunakan heap, dan algoritma pengurutan ini
dinamakan heapsort.

4/8
Heapsort

Misalkan kita memiliki array A berisi N elemen yang hendak


diurutkan menaik:
1. Muat seluruh elemen array A pada min-heap, menggunakan
makeHeap.
2. Untuk i dari 0 sampai N − 1, lakukan:
2.1 Dapatkan elemen terkecil dari heap dengan pop.
2.2 Tempatkan elemen ini pada A[i].

5/8
Analisis Heapsort

• Operasi makeHeap bekerja dalam O(N).


• Kemudian dilakukan N kali pop, sehingga kompleksitasnya
O(N log N).
• Jadi kompleksitas akhir heapsort adalah O(N log N).

6/8
Keuntungan Heapsort

• Heapsort memiliki keuntungan yang sama dengan selection


sort, yaitu dapat melakukan partial sort.
• Artinya, bila Anda hanya membutuhkan K elemen terkecil,
Anda dapat berhenti setelah melakukan pop sebanyak K kali.
• Kompleksitasnya menjadi O(N + K log N), yang lebih efisien
ketika K jauh lebih kecil dari N.

7/8
Penutup

• Kini Anda telah memahami 3 pengurutan dengan


kompleksitas O(N log N), setelah quicksort dan merge sort.
• Meskipun demikian, heapsort lebih jarang digunakan untuk
pengurutan N data, karena implementasinya yang cukup
rumit.
• Heapsort hanya digunakan untuk kebutuhan tertentu, seperti
partial sort.

8/8
Komputasi Geometri Dasar

Tim Olimpiade Komputer Indonesia

1/21
Pendahuluan

Melalui dokumen ini, kalian akan:


• Mengetahui dasar-dasar geometri yang digunakan pada
kompetisi pemrograman.
• Mengingat kembali definisi-definisi matematis elemen
geometri.

2/21
Titik

• Titik merupakan elemen mendasar pada dunia geometri.


• Titik dapat didefinisikan pada 2 dimensi, 3 dimensi, atau lebih.
• Pada persoalan pemrograman, umumnya titik didefinisikan
pada bidang 2 dimensi.
• Pada bidang 2 dimensi, titik dapat dianggap berada pada
suatu koordinat (x, y ).
• Representasi titik pada program dapat diwujudkan dengan
kelompok data yang menyimpan nilai x dan y .
• Kelompok data ini misalnya record (Pascal), struct (C) atau
class (C++/Java).

3/21
Garis
• Garis merupakan himpunan seluruh titik (x, y ), yang
memenuhi suatu persamaan Ax + By = C , dengan A, B, dan
C merupakan suatu bilangan riil.
• Bentuk lain dari persamaan garis yang umum adalah
y = mx + c, dengan m dan c suatu bilangan riil.

y
y=0.5x-2
4=x-2y

4 x
-2

4/21
Garis (lanj.)
• Untuk merepresentasikan garis, kita dapat membuat kelompok
data yang menyimpan nilai < A, B, C >, atau < m, c >,
bergantung pada persamaan yang Anda gunakan.
• Apabila ditelusuri, kedua persamaan ini sebenarnya berkaitan:

Ax + By = C
By = C − Ax
C A
y = − x
B
 B
A C
y = − x+
B B
A
• Jadi m = − B dan c = CB .

5/21
Garis Vertikal
• Hati-hati saat merepresentasikan garis vertikal, misalnya
x = 3.
• Representasi Ax + By = C dapat merepresentasikannya, yaitu
dengan A = 1, B = 0, C = 3.

x
x=3

6/21
Garis Vertikal (lanj.)

• Sementara representasi y = mx + c memiliki kesulitan, karena


nilai m yang tidak terdefinisi:
A
m = −
B
1
= −
0
• Untuk kasus yang mungkin terdapat garis vertikal,
representasi Ax + By = C lebih disarankan.

7/21
Segmen Garis
• Segmen garis merupakan garis yang terdefinisi dari suatu
titik (x1 , y1 ) ke titik (x2 , y2 )
• Perhatikan bahwa berbeda dengan segmen garis, garis
memiliki panjang yang tak berhingga.
• Segmen garis dapat direpresentasikan dengan dua titik, yaitu
ujung-ujungnya.

y
B(5,4)
A(-2,2)
4=x-2y

4 x
-2

8/21
Jarak Euclidean
• Jarak Euclidean adalah definisi jarak yang umum digunakan
pada bidang 2 dimensi atau ruang 3 dimensi.
• Jarak pada bidang 2 dimensi dari dua titik A(x, y) dan B(x, y)
adalah: p
dist(A, B) = (A.x − B.x)2 + (A.y − B.y )2
• Anda dapat menggunakan rumus ini untuk mengetahui
panjang dari segmen garis.

y
B(6,3)
82+62=10
6 x
A(-2,-3)

8
9/21
Segitiga

• Segitiga merupakan bangun 2 dimensi yang paling mendasar.


• Segitiga terdiri dari 3 titik sudut, yang mana antar titik sudut
terhubung oleh segmen garis.
• Berdasarkan panjang segmen garisnya, segitiga dapat berupa
segitiga sama kaki, sama sisi, siku-siku, atau sembarang.

10/21
Teorema Pythagoras

• Pada segitiga siku-siku, kita dapat mengetahui panjang sisi


miringnya menggunakan rumus Teorema Pythagoras.
a2 + b 2 = c 2

a c

11/21
Luas Segitiga
• Rumus klasik dari luas segitiga dengan panjang alas a dan
tinggi t adalah:
a×t
L=
2

12/21
Luas Segitiga (Rumus Heron)
• Ada kalanya kita tidak tahu tinggi dari segitiga, sehingga
rumus sebelumnya tidak dapat digunakan.
• Alternatif lain adalah menggunakan rumus Heron, yaitu:
p
L= s(s − a)(s − b)(s − c)

a+b+c
dengan s =
2

b c

13/21
Sudut Segitiga

• Jumlah sudut-sudut pada suatu segitiga adalah 180◦ .

22.5

67.5
90

14/21
Segi-N

• Untuk segi-N, jumlah sudut-sudutnya adalah 180(N − 2)◦ .


• Misalnya untuk segi-4, jumlah sudut-sudutnya adalah
180(4 − 2)◦ = 360◦

135 67.5

45 112.5

15/21
Lingkaran
• Lingkaran dengan titik pusat (xp , yp ) dan jari-jari r merupakan
himpunan seluruh titik (x, y ) yang memenuhi
(x − xp )2 + (y − yp )2 = r 2 .

y
r
(xp, yp)
x

16/21
Properti Lingkaran

• Luas dari lingkaran dengan jari-jari r adalah πr 2 .


• Keliling dari lingkaran dengan jari-jari r adalah 2πr .
• Diameter lingkaran merupakan dua kali jari-jari, atau 2r .

17/21
Tentang Pi
• Pi merupakan konstanta dalam matematika, dan digunakan
dalam pencarian luas atau keliling lingkaran.
22
• Hati-hati! Nilai pi tidak sama dengan .
7
22
• Penggunaan hanyalah aproksimasi dari nilai pi yang
7
sesungguhnya.

π = 3.14159265359..

22
= 3.14285714285..
7

18/21
Tentang Pi (lanj.)

• Untuk perhitungan pada kompetisi pemrograman, Anda dapat


menggunakan nilai pi yang diberikan pada soal.
• Jika soal tidak memberikan nilai pi yang digunakan, Anda
dapat mencarinya dengan fungsi arc-cosinus, yaitu fungsi pada
trigonometri.
• Nilai pi didapatkan dari:
• Pascal: arccos(−1)
• C++: acos(−1) (memerlukan include cmath)
• Trigonometri sendiri tidak termasuk dalam silabus kompetisi
pemrograman tingkat SMA, sehingga Anda tidak perlu
memperdalamnya.

19/21
Pesan Tentang Presisi

• Sebisa mungkin, gunakan tipe data bilangan bulat.


• Apabila Anda terpaksa menggunakan tipe data bilangan riil,
seperti real atau double, terapkan kiat-kiat yang telah Anda
pelajari pada pemrograman dasar.
• Sebagai contoh, untuk membandingkan dua bilangan, periksa
apakah selisih absolut dari kedua bilangan lebih kecil dari
suatu nilai toleransi, yang biasa disebut epsilon ().
isEqual(a, b)
return abs(a − b) < 
• Nilai toleransi yang biasa digunakan adalah 10−12 .

20/21
Penutup

• Komputasi geometri merupakan topik yang sangat luas, dan


baru kita bahas sebagian kecil.
• Pada kesempatan yang lain, kita akan mempelajari teknik
merepresentasikan elemen geometri menggunakan konsep
matematika yang lebih kompleks, yaitu vektor.

21/21
Memenangkan Kompetisi

Tim Olimpiade Komputer Indonesia

1/13
Kompetisi

Kompetisi pemrograman seperti ACM-ICPC, IOI, dan tentunya


OSN membutuhkan persiapan yang matang.

2/13
Mengenali Medan

Mengenali kondisi kompetisi yang Anda ikuti adalah hal yang


penting. Kondisi ini antara lain:
• Perhitungan poin/cara pemilihan pemenang.
• Jenis soal.
• Jumlah dan tingkat kesulitan soal.
• Waktu yang diberikan.
• Sumber daya yang ada.

3/13
Perhitungan Poin
Ada dua jenis cara perhitungan poin yang umum digunakan pada
kompetisi pemrograman:
• IOI-style
Anda bisa mendapatkan poin secara parsial, tergantung
subtask yang Anda kerjakan. Waktu pengerjaan tidak
berpengaruh pada hasil akhir. IOI dan OSN menggunakan
sistem penilaian ini.
• ICPC-style
Anda harus menyelesaikan masalah secara keseluruhan.
Dengan kata lain, nilai Anda adalah 0 atau 100. Penentuan
pemenang dihitung dari total soal yang diselesaikan. Waktu
pengerjaan dihitung sebagai penalti.

4/13
Jenis Soal

• Soal yang diberikan pada tingkat kompetisi umumnya


membutuhkan analisis yang dalam dan berlapis, khususnya
untuk IOI-style.
• Tidak mungkin Anda temukan soal: diberikan array , cetak
elemen-elemen dalam array itu secara terurut!
• Mengetahui teori-teori dan menghafal algoritma saja tidak
cukup.
• Yang diuji adalah kemampuan Anda melakukan observasi dan
analisis, untuk menyelesaikan masalah.

5/13
Jumlah dan Tingkat Kesulitan Soal

• OSN terdiri dari 3 soal perhari selama dua hari


• Kesulitan soal OSN bervariasi mulai dari mudah sampai sulit.
• Biasanya akan ada soal yang bertipe open problem. Soal ini
tidak memiliki solusi pasti. Artinya Anda akan membuat
program yang mendekati kebenaran sebaik mungkin.

6/13
Waktu dan Sumber Daya

• Waktu kontes OSN adalah 5 jam perhari.


• Perhatikan sumber daya berupa:
• Compiler yang disediakan.
• Text editor yang disediakan.
• Sistem operasi yang disediakan.
• Kemampuan mesin.
• Biasakan diri dengan sistem operasi, editor serta compiler
yang akan dipakai
• Informasi kemampuan mesin dapat digunakan untuk
memprediksi running time.

7/13
Persiapan Sebelum Kompetisi
• Lakukan latihan secara berkala sebelum kompetisi. Latihan
bisa dilakukan di:
• TLX
• Codeforces
• Topcoder
• SPOJ
• COCI
• dll
• Simulasikan OSN. Bisa menyelesaikan soal setingkat OSN saja
tidak cukup.
• Anda harus dapat menyelesaikannya dalam situasi yang serupa
saat kompetisi. Buatlah simulasi OSN dengan memilih 3 buah
soal, kemudian kerjakan sebaik-baiknya selama 5 jam tanpa
bantuan.

8/13
Tips Sebelum Kompetisi

• Istirahat cukup sebelum kontes berlangsung. Tidak dianjurkan


untuk begadang.
• Jika Anda sudah melakukan persiapan yang matang jauh-jauh
hari sebelumnya, beristirahatlah pada H-1 pertandingan agar
pikiran Anda tenang.

9/13
Tips Saat Kompetisi (lanj.)

• Disarankan untuk membaca semua soal terlebih dahulu


sebelum memulai mengerjakan
• Pastikan Anda sudah membaca semua soal.
• Jangan sampai ada penyesalan di akhir karena kehabisan
waktu untuk mengerjakan soal lain, sementara ada soal
lainnya yang ternyata bisa Anda kerjakan, luput dari perhatian
Anda.

10/13
Tips Saat Kompetisi (lanj.)

• Ketika menemukan suatu algoritma untuk menyelesaikan soal,


luangkan waktu beberapa menit untuk memikirkan kembali
algoritma tersebut matang-matang.
• Pastikan algoritma ini bekerja untuk soal yang akan Anda
selesaikan.
• Hal ini untuk menghindari membuang-buang waktu karena
Anda baru sadar ada kesalahan algoritma saat Anda sudah
mulai menulis program.

11/13
Tips Saat Kompetisi (lanj.)

• Lima jam adalah waktu yang panjang. Beristirahatlah sejenak


jika merasa lelah, karena ketika lelah, konsentrasi akan
berkurang
• Atur makanan Anda saat dan sebelum kontes. Jangan sampai
terlalu lapar atau terlalu kenyang.
• Sangat dianjurkan untuk mengerjakan semua soal, meskipun
tidak mendapatkan nilai sempurna. Anda bisa mendapatkan
beberapa poin ekstra dengan mengerjakan subsoal yang
diberikan.

12/13
Penutup

• Anda telah mempelajari dasar pemrograman kompetitif.


• Teruslah berlatih dan mempelajari hal baru untuk
meningkatkan kemampuan Anda.
• Bila Anda akan menghadapi kompetisi: selamat berkompetisi!
Semoga sukses dan jangan lupa untuk bersenang-senang :)

13/13

Anda mungkin juga menyukai