tag:blogger.com,1999:blog-28944264562961760402024-03-17T00:40:03.133+07:00Kupas KodeBlog kupas permasalahan bagi <em>competitive programmer</em>William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.comBlogger100125tag:blogger.com,1999:blog-2894426456296176040.post-2758954909008190592024-01-18T13:21:00.002+07:002024-01-18T15:33:12.388+07:00Tahun ke-10: Penutupan Blog<div style="display: none;"></div>Tanpa terasa sudah 10 tahun sejak blog ini dibuat. Saya telah menulis berbagai tutorial, pembahasan, atau sekedar berbagi pengalaman. Saya masih berharap tulisan-tulisan ini bermanfaat bagi kalian yang memulai perjalanan di dunia pemrograman kompetitif. Apabila demikian, maka saya akan merasa senang.<div> <div>Tulisan ini juga bermanfaat bagi saya sendiri, sebagai rekam jejak perjalanan hidup. Siapa sangka keputusan yang mengikuti seleksi olimpiade pada tahun 2009 akan mengubah hidup saya. Perjalanan mulai dari OSK, OSP, OSN, Pelatnas, IOI, regional ACM-ICPC, hingga ICPC World final adalah sebakul berkah. Begitu pula masa-masa yang saya alami bersama rekan-rekan alumni TOKI baik dalam melaksanakan program organisasi maupun menikmati waktu bersantai. Semua itu tidak akan saya lupakan.</div><div><br><div>
<div>Saya sudah tidak lagi aktif dalam dunia pemrograman kompetitif. Namun begitu banyak generasi muda di Ikatan Alumni TOKI yang mulai aktif dan berkontribusi. Mengetahui hal itu hati saya tenang bahwa ilmu programan kompetitif di Indonesia akan terus berkembang di tangan yang tepat.</div><div><br></div><div>Dengan tulisan ini, blog ini ditutup. Terima kasih atas dukungan selama ini.</div><div><br></div><div>-- William Gozali</div></div></div></div>William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-38944451822847494992020-05-09T09:04:00.000+07:002020-05-10T06:04:31.977+07:00Petunjuk Coding: Operasi Bit<div style="display: none;"></div>Beberapa tulisan saya menyinggung tentang operasi pada bit, seperti pada <a href="https://kupaskode.blogspot.com/2017/07/struktur-data-binary-indexed-tree-bit.html">struktur data Binary Indexed Tree</a> dan <a href="https://kupaskode.blogspot.com/2014/01/teknik-optimisasi-konstanta.html">teknik optimisasi konstanta</a>. Tulisan ini akan membahas semua yang saya ketahui tentang operasi bit. Menguasainya akan membantu Anda dalam implementasi solusi.<br />
<br />
<hr /><h3>Representasi Bit</h3>Komputer merepresentasikan angka dalam bentuk bilangan basis 2, atau nama lainnya <i>binary</i>/biner. Representasi ini hanya memiliki angka 0 atau 1.<br />
Sebuah digit pada bilangan biner disebut dengan bit.<br />
Sebagai catatan, bilangan yang kita biasa gunakan adalah basis 10, dengan digit berkisar antara 0 sampai 9.<br />
<br />
Perhatikan contoh bilangan biner berikut:<br />
<pre>1001101</pre><br />
Digit paling kanan menyatakan banyaknya \(2^0\), digit kedua dari kanan menyatakan banyaknya \(2^1\), dan seterusnya. Kita dapat mengetahui nilai bilangan tersebut dalam basis 10 dengan menjumlahkan seluruh nilai yang dinyatakan setiap bit:<br />
<pre>1 * 2^0 = 1
0 * 2^1 = 0
1 * 2^2 = 4
1 * 2^3 = 8
0 * 2^4 = 0
0 * 2^5 = 0
1 * 2^6 = 64
---- +
77</pre>Kini kita tahu hubungan antar representasi bilangan biner dan basis 10. Hubungan ini hanya berlaku untuk bilangan bulat (<i>integer</i>). Kita tidak membahas tentang bilangan riil, karena bentuk representasinya bergantung pada konteks.<br />
<br />
Cara serupa dapat digunakan untuk mengubah bilangan basis 10 ke basis 2. Namun, ada algoritma yang lebih praktis:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
string toBinary(int v) {
string s = "";
int x = v;
while (x > 0) {
s = to_string(x%2) + s;
x /= 2;
}
return s;
}
]]></script><!-----></div><br />
Prinsip kerja algoritma ini akan Anda ketahui setelah memahami operasi shift right yang akan dibahas di bawah.<br />
<a name='more'></a><br />
<hr /><h3>Integer 8, 16, 32, dan 64 Bit</h3>Bahasa pemrograman yang mengharuskan tipe data variabel umumnya menyediakan tipe data bilangan bulat dalam 8 bit, 16 bit, 32 bit, dan 64 bit. Sebagai contoh, berikut tipe data pada C++:<br />
<ul><li>8 bit: char</li>
<li>16 bit: short</li>
<li>32 bit: int</li>
<li>64 bit: long long</li>
</ul><div>Jika suatu bilangan memiliki x bit, artinya tipe tersebut dapat merepresentasikan 2<sup>x</sup> nilai yang berbeda. Oleh sebab itulah tipe data <b>long long</b> memiliki jangkauan bilangan yang lebih besar dari <b>int</b>.<br />
<br />
Semakin banyak bit juga berarti semakin banyak memori yang diperlukan. Pada komputer, 1 byte dapat memuat 8 bit. Dengan demikian, kita dapat mengetahui berapa ukuran suatu variabel dengan tipe data bilangan bulat. Misalnya pada C++:<br />
<ul><li>char: 1 byte</li>
<li>short: 2 byte</li>
<li>int: 4 byte</li>
<li>long long: 8 byte</li>
</ul><div>Ada hal penting yang belum dibahas, yaitu cara merepresentasikan bilangan negatif. Pada tipe data yang disebutkan di atas, komputer menggunakan bit paling kiri untuk menyatakan positif atau negatif. Nilai 0 berarti positif, dan 1 berarti negatif. Fakta ini juga mengakibatkan setiap tipe data kehilangan 1 bit untuk merepresentasikan angkanya. Misalnya pada <b>int</b>, sebenarnya hanya ada 31 bit untuk merepresentasikan besaran angkanya.</div><div><br />
</div><div>Mari bahas lebih dalam tentang bilangan negatif. Selain memiliki bit paling kiri bernilai 1, biner dari suatu bilangan -x sebenarnya direpresentasikan dengan biner (x-1), tapi semua bitnya dibalik (0 menjadi 1 dan sebaliknya). Perhatikan representasi biner seluruh bilangan untuk tipe data integer bohongan dengan 4 bit berikut:</div><pre> 7 0111
6 0110
5 0101
4 0100
3 0011
2 0010
1 0001
0 0000
-1 1111
-2 1110
-3 1101
-4 1100
-5 1011
-6 1010
-7 1001
-8 1000
</pre><br />
<div>Misalnya untuk -6, selain memiliki bit paling kiri bernilai 1, besarannya direpresentasikan dengan biner dari 5 (101) yang bitnya dibalik (010). Sehingga representasi -6 = 1010. Hal menarik lainnya adalah bit dari -1 sampai -8 merupakan pencerminan dari 0 sampai 7, dengan 0 diganti menjadi 1 (dan sebaliknya).<br />
<br />
Dengan memahami ini, kita jadi mengerti cara menghitung batas atas dan bawah suatu tipe data integer. Bila terdapat \(x\) bit, maka tipe tersebut hanya dapat merepresentasikan \(2^x\) nilai, yakni \(-2^{x-1}\) sampai dengan \(2^{x-1}-1\) . Sifat ini konsisten dengan batas bawah dan atas pada C++:<br />
<ul><li>int: \([-2^{31}, 2^{31}-1]\), atau \(-2147483648 .. 2147483647\)</li>
<li>long long: \([-2^{63}, 2^{63}-1]\)</li>
</ul><br />
Bahasa pemrograman seperti C++ menyediakan tipe data bilangan bulat yang menggunakan seluruh bit untuk merepresentasikan besaran angka:</div><div><ul><li>unsigned char</li>
<li>unsigned short</li>
<li>unsigned int</li>
<li>unsigned long long</li>
</ul><div>Karena tidak memiliki bit untuk menyatakan positif atau negatif, mereka hanya dapat meresentasikan bilangan non-negatif. Meskipun demikian, rentangnya lebih besar dari tipe data <i>signed</i>. Tipe data <i>unsigned </i>yang memiliki x bit mampu merepresentasikan bilangan dari 0 sampai 2<sup>x</sup>-1.</div></div><div><br />
</div><div>Sampai tulisan ini dibuat, belum ada tipe data 128 bit. Mungkin suatu saat akan muncul, dan saya harap C++ tidak menamakannya <b>long long long</b>.</div></div><br />
<hr /><h3>Operasi Bitwise</h3>Terdapat 6 operasi bitwise yang didukung pada bahasa pemrograman umum.<br />
<br />
<h4>not</h4>Simbol C++: ~<br />
Operasi ini akan membalik bit 0 menjadi 1, dan sebaliknya. Sebagai contoh, ~5 = -6.<br />
Perhatikan kembali daftar bilangan biner 4 bit yang di bagian atas tulisan ini.<br />
<br />
<h4>and</h4>Simbol C++: &<br />
Perhatikan bahwa simbolnya bukan &&, sebab && merupakan operator boolean <i>and</i>.<br />
Operator ini melibatkan 2 bilangan, dan menghasilkan suatu bilangan. Bit ke-i pada hasil bernilai 1 apabila bit ke-i pada kedua bilangan operand juga 1. Selain daripada itu, nilainya 0. Sifat ini mirip dengan operator boolean <i>and</i>.<br />
<br />
Sebagai contoh, berikut cara menghitung 6 & 3 pada tipe data int 8 bit:<br />
<pre>6 : 00000110
3 : 00000011
-----------------
6 & 3 : 00000010</pre><br />
Jadi hasilnya adalah 2.<br />
<br />
<h4>or</h4>Simbol C++: |<br />
Mirip dengan bitwise <i>and, </i>tapi bit ke-i pada hasil bernilai 1 kalau ada setidaknya satu operand-nya yang memiliki bit ke-i bernilai 1. Jadi syaratnya seperti operator boolean <i>or</i>.<br />
<br />
Sebagai contoh, berikut cara menghitung 6 | 3 pada tipe data int 8 bit:<br />
<pre>6 : 00000110
3 : 00000011
-----------------
6 | 3 : 00000111</pre><br />
Jadi hasilnya adalah 7.<br />
<br />
<h4>xor</h4>Simbol C++: ^<br />
Mirip dengan bitwise <i>or, </i>tapi bit ke-i pada hasil bernilai 1 kalau hanya ada satu operand-nya yang memiliki bit ke-i bernilai 1.<br />
<br />
Sebagai contoh, berikut cara menghitung 6 ^ 3 pada tipe data int 8 bit:<br />
<pre>6 : 00000110
3 : 00000101
-----------------
6 ^ 3 : 00000101</pre><br />
Jadi hasilnya adalah 5.<br />
<br />
<h4>shl</h4>Simbol C++: <<<br />
Shl adalah singkatan dari <i>shift left</i>. Operasi ini menerima 2 operand, dan berguna untuk menggeser operand pertama ke kiri sebanyak operand kedua. Bit paling kanan akan diisi dengan 0.<br />
Misalkan kita akan melakukan shl pada 6, dengan representasi biner berikut:<br />
<pre>6 : 00000110</pre><br />
Berikut adalah hasil shl dengan 1 dan 3:<br />
<pre>6 << 1 : 00001100 = 12
6 << 3 : 00110000 = 36</pre><br />
Kalau shl mengakibatkan bit paling kiri bernilai 1, berarti hasilnya adalah bilangan negatif.<br />
<br />
Sadari bahwa penggeseran sebanyak 1 bit ke kiri sama dengan perkalian dengan 2.<br />
Jadi penggeseran sebanyak x bit sama dengan perkalian dengan 2<sup>x</sup>.<br />
<br />
<h4>shr</h4>Simbol C++: >><br />
Shr adalah singkatan dari <i>shift right</i>. Bedanya dengan shl adalah operator ini menggeser bit ke kanan.<br />
Berikut adalah hasil shr dengan 1 dan 3:<br />
<pre>6 >> 1 : 00000011 = 3
6 >> 2 : 00000001 = 1</pre><br />
Sadari juga kalau penggeseran sebanyak 1 bit ke kanan sama dengan pembagian dengan 2, dibulatkan ke bawah. Jadi penggeseran sebanyak x bit sama dengan pembagian dengan 2<sup>x</sup>, dibulatkan ke bawah.<br />
<br />
Kini kita bisa memahami bagaimana algoritma mengubah bilangan basis 10 ke bentuk binernya. Pada setiap langkah, periksa sisa baginya terhadap 2. Kalau bernilai 1, dipastikan bit paling kanannya 1. Lalu buang bit paling kanannya dengan membaginya terhadap 2 dan dibulatkan ke bawah (setara dengan shr). Ulangi sampai bilangannya menjadi 0.<br />
<pre>77: 100110<u>1</u>
38: 10011<u>0</u>
19: 1001<u>1</u>
9: 100<u>1</u>
4: 10<u>0</u>
2: 1<u>0</u>
1: <u>1</u>
0: 0
</pre><br />
Koleksi bit paling kanan yang didapatkan setiap langkah dapat disusun menjadi representasi binernya. Untuk contoh 77, coba telusuri bit paling kanannya dari bawah ke atas, dan Anda akan mendapatkan binernya (yaitu 1001101).<br />
<br />
Urutan pengerjaan operator bitwise adalah: not, shl/shr, and, or, xor.<br />
Jika dipadukan dengan operator boolean, urutan pengerjaannya cukup memusingkan.<br />
Contohnya untuk (6 & 2 == 0), operator == akan dikerjakan dulu, sehingga didapatkan (6 & false). Supaya aman, selalu gunakan tanda kurung seperti ((6 & 2) == 0). Ini adalah praktik yang disarankan Bu Inge, menurut ingatan zaman Pelatnas 1 saya.<br />
<br />
<hr /><h3>Operasi Lanjutan dan Manipulasi Bit</h3><h4>Memeriksa nilai bit ke-i</h4>Idenya adalah menggunakan <i>and</i> terhadap (1<<i). Jika nilainya lebih dari 0, artinya bit ke-i menyala.<br />
Simak kedua contoh berikut.<br />
<pre>5 & (1<<2) = 0
00000101
00000010
--------
00000000</pre><br />
<pre>5 & (1<<3) = 4
00000101
00000100
-------- &
00000100</pre><br />
<br />
<h4>Mengubah nilai bit ke-i</h4>Untuk menyalakan bit ke-i, gunakan <i>or</i> terhadap (1<<i):<br />
<pre>5 | (1<<2) = 7
00000101
00000010
-------- |
00000111</pre><br />
Untuk membalik bit ke-i, gunakan <i>xor</i> terhadap (1<<i):<br />
<pre>5 ^ (1<<3) = 1
00000101
00000100
-------- ^
00000001</pre><br />
Sementara untuk mematikan bit ke-i, caranya dengan and terhadap biner yang sepenuhnya bernilai 1 kecuali bit ke-i. Bilangan tersebut adalah ~(1<<i).<br />
<pre>5 & ~(1<<3) = 1
00000101
11111011
-------- &
00000001</pre><br />
<h4>Menghitung banyaknya bit 1</h4>Cara berikut diajarkan Pak Suhendry, idenya membuang bit 1 paling kanan satu demi satu sambil menghitung berapa kali ini dilakukan.<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
int count1(int x){
int ret = 0;
while (x){
ret++;
x = x & (x-1);
}
return ret;
}
]]></script><!-----></div><br />
<h4>Membuang bit terkanan yang menyala</h4>Operasi x = x & (x-1) menjamin x kehilangan 1 bit paling kanan.<br />
<pre>12 & (12-1) = 8
00001100
00001011
-------- &
00001000</pre><br />
<h4>Mengambil nilai bit terkanan yang menyala</h4>Kalau operasi x = x & (x-1) menghapus bit 1 terkanan, operasi x & -x berguna untuk mengambil bit 1 terkanan.<br />
<pre>12 & -12 = 4
00001100
11110100
-------- &
00000100</pre>Operasi ini digunakan pada struktur data BIT.<br />
<br />
<br />
<h4>Enumerasi subset</h4>Diberikan N barang, yang dinomori dari 0 sampai N-1. Cetak semua kombinasi pemilihan barang!<br />
<br />
Solusi yang langsung terpikir adalah dengan rekursi. Ada solusi lain dengan mengenumerasi seluruh bilangan dari \(0\) sampai \(2^N-1\), lalu periksa bit mana saja yang menyala.<br />
<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
#include <bits/stdc++.h>
using namespace std;
void enumerate(int n) {
for (int i = 0; i < (1<<n); i++) {
for (int j = 0; j < n; j++) {
if (i & (1<<j)) {
printf("%d ", j);
}
}
printf("\n");
}
}
int main() {
int N;
scanf("%d", &N);
enumerate(N);
}
]]></script><!-----></div><br />
<h4>Enumerasi subset dari subset</h4>Cara ini juga diajarkan Pak Suhendry.<br />
Diberikan bilangan biner P, cetak semua kombinasi biner tidak 0 yang ada, apabila hanya bit 1 pada P boleh dinyalakan atau dimatikan. Bit 0 haruslah tetap 0.<br />
<br />
Misalnya untuk P=13, keluaran yang diharapkan adalah 7 kemungkinan berikut (bagian kanan menyatakan representasi binernya):<br />
<pre>13 1101
12 1100
9 1001
8 1000
5 0101
4 0100
1 0001
</pre><br />
Solusinya adalah dengan menyimpan P ke dalam suatu variabel, misalnya x. Lalu kurangkan x dengan 1, dan <i>and</i> dengan P. Dengan demikian, bit 0 selalu tetap 0. Lakukan proses ini sampai x bernilai 0. Implementasinya ditunjukkan pada fungsi enumerate berikut:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
#include <bits/stdc++.h>
using namespace std;
string toBinary(int v) {
string s = "";
int x = v;
while (x > 0) {
s = to_string(x%2) + s;
x /= 2;
}
return s;
}
void enumerate(int p) {
int x = p;
while (x) {
printf("%2d %12s\n", x, toBinary(x).c_str());
x = (x-1) & p;
}
}
int main() {
int P;
scanf("%d", &P);
enumerate(P);
}
]]></script><!-----></div><br />
Cara ini berguna kalau sewaktu-waktu Anda perlu mengenumerasi semua subset dari suatu subset.<br />
<br />
<hr /><h3>Contoh Kegunaan</h3>Manipulasi bit biasa digunakan untuk DP bitmask. Bila Anda belum tahu DP bitmask, sebenarnya dia hanyalah DP yang statenya berupa himpunan. Perhatikan contoh soal berikut:<br />
<ul><li>Ada N ekor bebek, yang dinomori dari 0 sampai dengan N-1.</li>
<li>Pak Dengklek sedang melelang M barang, yang dinomori dari 0 sampai dengan M-1. </li>
<li>Bebek ke-i bersedia membeli barang lelangan ke-j dengan harga H[i][j] Rupiah.</li>
<li>Jika setiap barang hanya bisa dibeli oleh paling banyak seekor bebek, dan setiap bebek hanya boleh membeli paling banyak 1 barang, maka berapa penghasilan maksimal Pak Dengklek yang mungkin?</li>
<li>Batasan:</li>
<ul><li>1 ≤ N ≤ 10</li>
<li>1 ≤ M ≤ 1000</li>
<li>N ≤ M</li>
<li>1 ≤ H[i][j] ≤ 100000</li>
</ul></ul><br />
Solusinya adalah mencoba semua kemungkinan untuk setiap barang:<br />
<ol><li>Barang ini tidak dibeli bebek manapun</li>
<li>Barang ini dibeli salah satu bebek yang belum pernah membeli barang</li>
</ol><div>Supaya pilihan di atas dapat dilaksanakan, kita perlu menyimpan <i>state</i> berupa: indeks barang yang sedang dicoba dan bebek mana saja yang sudah pernah membeli barang. <i>State </i>kedua ini berupa himpunan, misalnya {0, 3, 4} menyatakan bebek nomor 0, 3, dan 4 sudah pernah membeli barang. </div><div><br />
</div><div>Berikut formulasinya:</div>\(<br />
f(x,S) = \left\{\begin{array}{lr}<br />
0 & ,x=M\\<br />
\displaystyle \max \left( f(x+1, S), \max_{\substack{0 \leq i \le N \\ i \notin S}} f(x+1, S \cup \{i\}) + H[i][x] \right) &, x < M\\ \end{array}\right. \)<br />
<br />
<div>Supaya mudah, kita akan merepresentasikan himpunan \(S\) dalam bilangan biner. Bit ke-x bilangan ini bernilai 1 kalau bebek nomor x sudah pernah membeli. Jadi untuk {0, 3, 4}, nilainya adalah 11001 (atau 25 dalam basis 10). Sebab kita menggunakan bit untuk menyatakan himpunanlah yang membuat strategi ini dinamakan DP bitmask.</div><br />
Berikut adalah implementasinya, dengan asumsi array dp awalnya diisi dengan nilai -1.<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
int N, M;
int H[10][1000];
int dp[1001][1<<10];
int f(int x, int mask) {
if (x == M) {
return 0;
} else if (dp[x][mask] != -1) {
return dp[x][mask];
} else {
// Coba barang x tidak dibeli siapapun
int dp[x][mask] = f(x+1, mask);
for (int i = 0; i < N; i++) {
// Periksa apakah bebek ke-i belum beli barang
if ((mask & (1<<i)) == 0) {
// Coba barang x dibeli bebek ke-i
dp[x][mask] = max(dp[x][mask], f(x+1, mask | (1<<i)) + H[i][x]);
}
}
return dp[x][mask];
}
}
// Jawaban ada di f(0, 0)
]]></script><!-----></div><br />
<hr /><h3>Penutup</h3>Anda dapat berlatih menggunakan operasi bitwise pada soal DP bitmask berikut:<br />
<ul><li><a href="https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=146&page=show_problem&problem=1592">UVa 10651 - Pebble Solitaire</a></li>
<li><a href="https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=1437">UVa 10496 - Collecting Beepers</a></li>
<li><a href="https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=146&page=show_problem&problem=1852">UVa 10911 - Forming Quiz Teams</a></li>
</ul><br />
Demikianlah tulisan saya kali ini.<br />
Semoga membantu pemahaman tentang biner, bit, operasinya, dan aplikasinya.<br />
William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-37187872395426656642020-05-03T07:08:00.000+07:002020-05-10T09:24:16.670+07:00Pembahasan Final JOINTS PCS 2019<div style="display: none;"></div>Tulisan terakhir dari seri pembahasan JOINTS PCS 2019.<br />
Soal-soalnya dapat dilihat dan dicoba di <a href="https://tlx.toki.id/problems/joints-2019-pcs-final">https://tlx.toki.id/problems/joints-2019-pcs-final</a>.<br />
<br />
<hr /><h3>Coin Exchange 2</h3>Solusinya mirip dengan soal Coin Exchange pada penyisihan. Cukup coba iterasi dari \(N/2\) menuju ke 2. Begitu ditemukan nilai yang FPB-nya dengan \(N\) sama dengan 1, hentikan pencarian dan cetak jawaban.<br />
<br />
<hr /><h3>Dolan 2020</h3>Pertama, sederhanakan soal dengan menganggap suatu sel berisi 'P' sama dengan 5 sel berisi 'L':<br />
<pre>..... .....
..... ..L..
..P.. = .LLL.
..... ..L..
..... .....
</pre><br />
Sekarang tugas kita tinggal mencari persegi panjang yang sepenuhnya hanya terdiri dari 'O'.<br />
Apabila diproses baris per baris, sadari bahwa soal ini menjadi mirip dengan suatu soal klasik: diberikan sebuah histogram, cari luas persegi panjang terbesar yang sepenuhnya dikandung histogram tersebut.<br />
<br />
Misalkan kita memiliki konfigurasi berikut dan sedang memproses baris ketiga. Supaya lebih mudah dilihat, tanda titik menyatakan 'O'.<br />
<pre>LL.L..L.
.L....LL
....L...</pre><br />
Dari baris ketiga, struktur ini dapat dipandang seperti histogram berikut, dengan 'X' menyatakan bagian dari batangan histogram.<br />
<pre> X X
X XX X
XXXX XXX
</pre><div><br />
</div>Terdapat algoritma \(O(N)\) untuk mencari luas persegi panjang terbesar di dalam histogram dengan \(N\) batangan. Idenya adalah untuk setiap batangan pada histogram dengan tinggi \(h\), cari indeks sekiri dan sekanan mungkin sehingga seluruh ketinggian batangan histogram di antara keduanya \(\ge h\). Bila kedua indeks tersebut adalah \(a\) dan \(b\), berarti luas persegi panjang dengan panjang \(h\) dan lebar \((b-a+1)\) menjadi kandidat solusi.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQK7OFRDk9UNf0g5IB_zfbBFW-327KPuPZnX9WG4U8npISmG6QHdAyKz_HCEhPm0ljY4yEkdttL5yQGriGehGvMk5FE9KLAKycVSixHW_MpGSBuoIuuVJl7kh6ME7Npq-0f7uWRN_mrv6m/s1600/g7463.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="363" data-original-width="500" height="232" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQK7OFRDk9UNf0g5IB_zfbBFW-327KPuPZnX9WG4U8npISmG6QHdAyKz_HCEhPm0ljY4yEkdttL5yQGriGehGvMk5FE9KLAKycVSixHW_MpGSBuoIuuVJl7kh6ME7Npq-0f7uWRN_mrv6m/s320/g7463.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Ilustrasi persegi panjang terbesar yang muat dan setinggi batangan yang berwarna gelap.</td></tr>
</tbody></table>Mencari indeks terkiri dan terkanan untuk setiap batangan dapat dilakukan secara efisien dengan bantuan <i>monotonic stack. </i>Gunakan saja <i>stack </i>biasa, yang menyimpan indeks batangan. Syarat khusus yang menjadikannya <i>monotonic </i>adalah tinggi batangan untuk setiap indeks dalam <i>stack</i> wajib menaik.<br />
<a name='more'></a>Proses setiap batangan dari kiri ke kanan, sambil menyimpan <i>stack</i> yang isinya hanya boleh menaik. Misalkan \(h[i]\) menyatakan tinggi batangan histogram di indeks ke-i. Untuk suatu batangan ke-\(i\):<br />
<ol><li>Selama <i>stack </i>tidak kosong dan \(h[stack.top()] \ge h[i]\):</li>
<ol><li>Ambil elemen paling atas <i>stack</i>, sebut saja \(t\), lalu buang dia dari <i>stack.</i></li>
<li>Indeks sekiri dan sekanan mungkin sehingga seluruh ketinggian batangan histogram di antara keduanya \(\ge h[t]\) ada di \(stack.top()+1\) dan \(i-1\). Kalau ternyata <i>stack </i>sudah kosong, berarti indeks terkirinya 0. Perhatikan gambar di bawah untuk intuisi mengapa ini benar.</li>
</ol><li>Baru masukkan \(i\) ke dalam \(stack\). Karena seluruh indeks yang tingginya lebih dari \(i\) sudah dibuang, dipastikan tinggi batangan pada indeks di <i>stack </i>ini terurut menaik.</li>
</ol><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMjLwB-GHzaKLv_Rp07YGFM6tZtuSJUapAkO7Bs45KvQqn6afJdKlr_YMkqhi6GP2ae61_EjlAWIrOYICWEl9CFAidDEJYbpnVSULU0aSkq7jvnvk3FSzGVi3WL6UekmY-LuV5d5lGpfZg/s1600/g7669.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="513" data-original-width="550" height="372" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMjLwB-GHzaKLv_Rp07YGFM6tZtuSJUapAkO7Bs45KvQqn6afJdKlr_YMkqhi6GP2ae61_EjlAWIrOYICWEl9CFAidDEJYbpnVSULU0aSkq7jvnvk3FSzGVi3WL6UekmY-LuV5d5lGpfZg/s400/g7669.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">(i) Kondisi awal <i>stack</i>, batangan yang berwarna gelap berada di dalam <i>stack</i>.<br />
(ii) Penambahan batangan terbaru di paling kanan mengakibatkan pembuangan elemen <i>top of stack</i>, sambil menghitung luas persegi panjang terbesar yang muat dan setinggi batangan tersebut.<br />
(iii) Peristiwa yang sama dengan (ii) masih perlu dilakukan karena tinggi elemen pada <i>top of stack </i>masih lebih dari tinggi elemen yang hendak dimasukkan.<br />
(iv) Kini batangan terbaru dapat dimasukkan ke dalam <i>stack</i>.</td></tr>
</tbody></table><br />
Lakukan hal tersebut sampai batangan paling kanan. Terakhir, proses semua elemen tersisa yang masih ada dalam <i>stack</i>. Karena saya malas mengetik kode lagi, saya buat saja ada batangan baru setelah indeks terakhir dengan tinggi 0.<br />
<br />
Setiap batangan pasti di-<i>push</i> sekali, dan di-<i>pop </i>sekali. Setiap kali suatu batangan di-<i>pop, </i>kita menghitung persegi panjang terbesar yang bisa dibentuk dengan batangan tersebut. Dengan cara ini, seluruh kemungkinan persegi panjang optimal telah dicoba.<br />
<br />
Apabila algoritma ini diterapkan di seluruh baris, kompleksitasnya menjadi \(O(RC)\). Pas untuk accepted.<br />
<br />
<hr /><h3>Gim Punakawan</h3>Soal ini memiliki aroma DP semacam Fibonacci yang sangat kuat. Lalu melihat batasan \(N\) yang diberikan, sudah pasti ini perlu diselesaikan dengan perkalian matriks. Jadinya kita perlu mencari fungsi yang bentuknya seperti:<br />
\[f(x) = k_1f(x-1) + k_2f(x-2) + k_3f(x-3) + ...\]<br />
Misalkan S, G, P, dan B berturut-turut menyatakan Semar, Gareng, Petruk, dan Bagong. Tugas kita menghitung banyaknya kemungkinan string yang hanya terdiri dari keempat karakter tersebut, memiliki panjang \(N\), dan memenuhi tabiat setiap punakawan.<br />
<br />
Misalnya kita masih perlu memanggil \(x\) punakawan. Kita memiliki pilihan:<br />
<ol><li>Memanggil S. Pemanggilan selanjutnya tidak boleh S lagi. Karena tidak ingin menyimpan <i>state </i>tambahan dalam DP-nya, langsung saja kita tentukan siapa yang akan dipanggil selanjutnya. Kemungkinannya adalah: P, G, B. Berhubung pemanggilan B harus disusul dengan P, berarti pilihan sebenarnya adalah P, G, BP.<br />
Jadi, ada 3 pilihan kalau kita memanggil S: SP, SG, SBP.</li>
<li>Memanggil G. Tidak ada syarat tambahan, dan hanya ada 1 pilihan: G</li>
<li>Memanggil P. Tidak ada syarat tambahan, dan hanya ada 1 pilihan: P</li>
<li>Memanggil B, yang mana harus disusul dengan P. Jadi pilihannya: BP</li>
</ol><br />
Keempat kemungkinan melahirkan 6 pilihan string untuk setiap pemanggilan, dengan panjang masing-masing 2, 2, 3, 1, 1, dan 2 (untuk SP, SG, SBP, G, P, dan BP).<br />
<br />
Definisikan \(f(x)\) sebagai banyaknya kemungkinan string dengan panjang \(x\) karakter yang dapat dibuat. Dengan pilihan berupa SP, SG, SBP, G, P, dan BP, maka nilai \(f(x)\) sama dengan:<br />
\[\begin{array}{rcl} f(x) &=& f(x-2)+f(x-2)+f(x-3)+f(x-1)+f(x-1)+f(x-2) \\<br />
&=& f(x-3) + 3f(x-2) + 2f(x-1)<br />
\end{array}\]<br />
Fungsi ini rekursif, dan base case dicapai saat \(f(1)=4\), karena ada 4 kemungkinan string: S, G, P, B.<br />
Definisikan juga \(f(0) = f(-1) = 1\), untuk mempermudah perhitungan \(f(2)\) dan \(f(3)\). Untuk memastikan fungsi kita benar, boleh dicoba menghitung \(f(2)\) dan \(f(3)\):<br />
\[\begin{array}{rcl} f(2) &=& f(-1) + 3f(0) + 2f(1) \\<br />
&=& 1 + 3 \cdot 1 + 2 \cdot 4 \\<br />
&=& 12<br />
\end{array}\]<br />
\[\begin{array}{rcl} f(3) &=& f(0) + 3f(1) + 2f(2) \\<br />
&=& 1 + 3 \cdot 4 + 2 \cdot 12 \\<br />
&=& 37<br />
\end{array}\]<br />
Sudah sama dengan contoh masukan! Kini tinggal diubah ke versi perkalian matriks.<br />
Kita perlu membuat 2 matriks, yaitu matriks basis dan rekurensnya.<br />
<br />
Untuk basis, yang selanjutnya dinamakan dengan \(A\), matriksnya berukuran \(1 \times k\), dengan \(k\) adalah elemen paling lama yang dibutuhkan. Pada rumus \(f(x)\), elemen paling lama yang dibutuhkan adalah \(f(x-3)\). Jadi ukurannya \(1 \times 3\). Isi dari matriks ini adalah 3 suku berurutan pertama, yaitu \(f(-1), f(0), f(1)\):<br />
\[A = \left[ \begin{array}{ccc}1 & 1 & 4 \end{array} \right]\]<br />
Untuk rekurens, yang selanjutnya dinamakan dengan \(B\), matriksnya berukuran \(k \times k\). Perhatikan bahwa hasil \(A \cdot B\) adalah matriks berukuran \(1 \times k\). Tujuannya adalah membuat \(A \cdot B\) berisi \(f(0), f(1), f(2)\):<br />
\[A \cdot B = \left[ \begin{array}{ccc}1 & 4 & 12 \end{array} \right]\]<br />
Dengan sedikit corat-coret, nilai matriks \(B\) adalah:<br />
\[B = \left[ \begin{array}{ccc} 0 & 0 & 1 \\ 1 & 0 & 3 \\ 0 & 1 & 2 \end{array} \right]\]<br />
Kolom pertama dan kedua berguna untuk menggeser isi matriks \(A\) ke kiri. Sementara kolom ketiga untuk menghitung nilai \(f\) berikutnya.<br />
<br />
Sadari bahwa \(A \cdot B\) dapat dikalikan lagi dengan \(B\), menghasilkan \(f(1), f(2), f(3)\):<br />
\[A \cdot B^2 = \left[ \begin{array}{ccc} 4 & 12 & 37 \end{array} \right]\]<br />
Dengan mengulang proses ini, pencarian \(f(N)\) dapat dilakukan dengan menghitung \(AB^{N-1}\) dan mengambil elemen ketiganya.<br />
<br />
Pemangkatan matriks dapat menggunakan <i>fast exponentiation</i> biasa. Kompleksitas akhir solusinya adalah \(O(\log{N})\). Anda dapat melihat implementasinya pada kode yang saya cantumkan di akhir tulisan.<br />
<br />
<hr /><h3>Kotak Ajaib</h3>Misalnya diberikan angka berikut:<br />
<pre>1 6 5 2 3 10 14</pre><div><br />
Bila elemen terakhir harus dipilih sebagai indeks terakhir, maka ada berapa kemungkinan indeks pertama sehingga jumlah bilangan antar keduanya habis dibagi 4?</div><br />
Untuk mendapatkan jawabannya, mari hitung jumlahan kumulatifnya:<br />
<span style="font-family: monospace; white-space: pre;">kumulatif: 1 7 12 14 17 27 41</span><br />
<br />
Kita perlu mencari banyaknya prefix, sehingga nilai 41 dikurangi prefix tersebut habis dibagi 4.<br />
Berhubung kita hanya peduli dengan sisa bagi terhadap 4, mari cari sisa baginya terhadap 4:<br />
<span style="font-family: monospace; white-space: pre;">kumulatif: 1 7 12 14 17 27 41</span><br />
<span style="font-family: monospace; white-space: pre;">mod 4 : 1 3 0 2 1 3 1</span><br />
<br />
Kini lebih terlihat bahwa kita cukup mencari prefix yang sisa bagi jumlahannya terhadap 4 sama dengan sisa bagi jumlahan kumulatif sejauh ini (yaitu 1). Terdapat 2 kemungkinan, yaitu posisi yang jumlahan kumulatifnya 1 atau 17. Apabila kita membuang prefix tersebut, didapatkan hasil yang habsi dibagi 4:<br />
<ul><li>41 - 1 = 40 (jumlah subbarisan 6, 5, 2, 3, 10, 14)</li>
<li>41 - 17 = 28 (jumlah subbarisan 10, 14)</li>
</ul>Dengan ide ini, berarti cukup cari jumlahan kumulatif sejauh ini mod 4, dan cari banyaknya pilihan jumlahan kumulatif mod 4 di depannya yang bernilai sama. Solusi ini dapat diimplementasi dalam \(O(N)\).<br />
<br />
<hr /><h3>Air Mancur Gelas</h3>Operasi yang dilakukan pada subtree biasa dapat diubah ke bentuk range update, dengan cara mengubah tree menjadi array sesuai dengan urutan <i>in-order traversal</i>.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyOT8Jaz9Yjkj25WHUjKKIxQkzgyN0a9g8YbdVDW_otxo2IaaZclz3_K2u-fpXJJy0t06C2_Ksf1wtRRHg3E4lz6dXGu1k781lQ6qqb3h5Xo4gqyFJQPd4bBGZt4YxrdKvjXd2tQ55-Cp_/s1600/g8247.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="548" data-original-width="500" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyOT8Jaz9Yjkj25WHUjKKIxQkzgyN0a9g8YbdVDW_otxo2IaaZclz3_K2u-fpXJJy0t06C2_Ksf1wtRRHg3E4lz6dXGu1k781lQ6qqb3h5Xo4gqyFJQPd4bBGZt4YxrdKvjXd2tQ55-Cp_/s400/g8247.png" width="363" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Urutan masuk dan keluar setiap node pada <i>in-order traversal</i>.<br />
Gambar bagian bawah menunjukkan kapan suatu <i>node</i> mulai dan selesai dikunjungi.</td></tr>
</tbody></table>Misalnya L[x] menyatakan urutan node x mulai dikunjungi dan R[x] menyatakan urutan node x selesai dikunjungi. Operasi menambahkan seluruh subtree x dengan v sama saja dengan melakukan range update +v pada indeks L[x] sampai R[x].<br />
<br />
Operasi yang lain adalah mencari berapa nilai suatu node, yang dapat dijawab dengan mencari nilai pada indeks L[x].<br />
<br />
Struktur data paling mudah untuk melayani range update dan single query seperti ini adalah Binary Index Tree (BIT). Ingat bahwa BIT mendukung dua operasi: update(i, v) yang menambah nilai v pada indeks ke-i, dan query(i) yang mencari jumlahan dari indeks ke-1 sampai ke-i. Kalau belum tahu tentang BIT, silakan baca <a href="https://kupaskode.blogspot.com/2017/07/struktur-data-binary-indexed-tree-bit.html">tulisan ini</a>.<br />
<br />
Untuk range update +v di indeks antara a dan b, lakukan:<br />
<ol><li>update(a, v)</li>
<li>update(b+1, -v)</li>
</ol><div>Sementara pencarian nilai di indeks x dapat dilakukan dengan mencari query(x).</div><br />
Solusi ini bekerja dalam \(O(Q \log{N})\).<br />
<br />
<hr /><h3>Kelas Matematika</h3>Ini soal tersulit, dan saya menghabiskan waktu beberapa jam memikirkannya. Pada akhirnya saya menyerah dan bertanya ke teman-teman lain. Ammar mendapatkan solusinya, yang ternyata mengejutkan. Rasanya tidak mungkin saya menemukannya.<br />
<br />
Pertama, ubah dulu bentuk persamaannya:<br />
\[<br />
\begin{eqnarray*}<br />
\frac{xy}{r} &=& x - y \\<br />
xy &=& rx - ry \\<br />
xy + ry &=& rx \\<br />
y(x + r) &=& rx \\<br />
y &=& \frac{rx}{x+r}<br />
\end{eqnarray*}<br />
\]<br />
Nilai \(y\) adalah bilangan bulat. Kini diketahui bahwa \(rx\) harus habis dibagi \(x+r\), atau dengan kata lainnya \(x+r\) pastilah faktor dari \(rx\).<br />
<br />
Berhubung nilai \(x\) tidak diketahui, terdapat sebuah trik untuk mengubah nilai pembilang supaya sepenuhnya menjadi nilai yang diketahui. Untuk kasus ini, tambahkan pembilang dengan \(r^2 - r^2\):<br />
\[<br />
\begin{eqnarray*}<br />
y &=& \frac{rx+r^2-r^2}{x+r} \\<br />
&=& \frac{r(x+r)-r^2}{x+r} \\<br />
&=& r + \frac{-r^2}{x+r} \\<br />
&=& r + \frac{r^2}{-x-r} \\<br />
\end{eqnarray*}<br />
\]<br />
Pembilangnya menjadi \(r^2\), yang nilainya diketahui. Jadi \(-x-r\) haruslah faktor dari \(r^2\). Misalnya diketahui \(d\) adalah suatu faktor \(r^2\), nilai \(x\) dapat dicari dengan:<br />
\[<br />
\begin{eqnarray*}<br />
-x-r &=& d \\<br />
-x &=& d +r \\<br />
x &=& -d -r<br />
\end{eqnarray*}<br />
\]<br />
Sebagai contoh, pada soal diberikan \(r=3\). Faktor dari \(r^2\) adalah 1, 3, dan 9. Maka nilai \(x\) yang mungkin adalah:<br />
<ul><li>-1-3 = -4</li>
<li>-3-3 = -6</li>
<li>-9-3 = -12</li>
</ul><div>Setelah dijelaskan Ammar, saya butuh waktu untuk mencerna keajaiban ini.<br />
<br />
Sekarang kita hanya perlu mencari seluruh faktor dari \(r^2\).<br />
<br />
Misalnya \(p_1, p_2, p_3, \ldots, p_k\) menyatakan \(k\) bilangan prima pertama, dan \(e_1, e_2, e_3, \ldots, e_k\) menyatakan suatu bilangan asli. Nilai \(r\) dapat dituliskan dalam bentuk faktorisasi prima:<br />
\[r = p_1^{e_1} \cdot p_2^{e_2} \cdot p_3^{e_3} \cdot \ldots \cdot p_k^{e_k}\]<br />
Untuk \(r^2\), faktorisasi primanya sesederhana kalikan pangkatnya dengan dua:</div>\[r^2 = p_1^{2e_1} \cdot p_2^{2e_2} \cdot p_3^{2e_3} \cdot \ldots \cdot p_k^{2e_k}\]<br />
Kini tinggal brute force untuk setiap faktor prima \(p_i\), seberapa banyak faktor prima ini akan digunakan. Misalkan banyaknya dinyatakan sebagai \(q_i\). Nilai \(q_i\) berkisar antara 0 sampai \(2e_i\). Setelah seluruh \(q_i\) ditentukan, perkalian dari seluruh \(p_i^{q_i}\) merupakan faktor dari \(r^2\).<br />
<br />
Supaya lebih jelas, ambil contoh \(r=12\). Faktorisasi primanya:<br />
\[12=2^2 \cdot 3^1\]<br />
Faktorisasi prima untuk \(r^2\):<br />
\[144=2^4 \cdot 3^2\]<br />
Dan berikut ialah seluruh faktor dari 144:<br />
\[\begin{array}{l|l|l}<br />
2^0 \cdot 3^0 = 1 & 2^0 \cdot 3^1 = 3 & 2^0 \cdot 3^2 = 9 \\<br />
2^1 \cdot 3^0 = 2 & 2^1 \cdot 3^1 = 6 & 2^1 \cdot 3^2 = 18 \\<br />
2^2 \cdot 3^0 = 4 & 2^2 \cdot 3^1 = 12 & 2^2 \cdot 3^2 = 36 \\<br />
2^3 \cdot 3^0 = 8 & 2^3 \cdot 3^1 = 24 & 2^3 \cdot 3^2 = 72 \\<br />
2^4 \cdot 3^0 = 16 & 2^4 \cdot 3^1 = 48 & 2^4 \cdot 3^2 = 144 \\<br />
\end{array}\]<br />
Masing-masing faktor itu tinggal dijadikan negatif, lalu dikurangi \(r\), dan didapatkanlah suatu kemungkinan \(x\).<br />
<br />
Faktorisasi prima dapat diimplementasikan dalam \(O(\sqrt{r})\), sehingga kompleksitas solusi ini adalah \(O(\sqrt{r} + K)\) dengan \(K\) menyatakan banyaknya jawaban.<br />
<br />
<hr /><h3>Typo!</h3>Soal ini kelihatan seperti DP, tapi sebenarnya tidak perlu DP. Sebab hanya boleh ada 1 typo, jadi bisa saja kita coba semua kemungkinannya.<br />
<br />
Pertama, periksa apakah keduanya sama. Kalau ya, berarti bisa login.<br />
Kalau tidak, periksa apakah panjang password asli <8. Kalau ya, berarti dijamin tidak bisa login.<br />
<br />
Selain daripada itu, cari berapa nilai panjang password asli dikurangi panjang password diketik:<br />
<ol><li>Jika lebih dari 1 atau negatif, dipastikan tidak bisa login</li>
<li>Jika sama dengan 1, berarti ada karakter yang hilang</li>
<li>Jika sama dengan 0, berarti mungkin ada karakter yang salah diketik</li>
</ol>Untuk kasus kedua, coba semua kemungkinan menghilangkan 1 karakter di password asli. Kalau ada kemungkinan setelah dibuang 1 karakter dan cocok dengan password diketik, berarti bisa login.<br />
<br />
Untuk kasus ketiga, periksa banyaknya karakter yang berbeda. Kalau tidak lebih dari 1, berarti bisa login.<br />
<br />
Selain seluruh kasus di atas, berarti tidak bisa login.<br />
Kompleksitasnya \(O(N^2)\) dengan \(N\) menyatakan panjang password. Ini terjadi saat kasus kedua dijumpai, karena kita harus mencocokkan string untuk \(N\) kemungkinan karakter yang hilang, dan sekali pencocokan string kompleksitasnya \(O(N)\).<br />
<br />
<hr /><h3>Smart Village</h3>Mari selidiki dulu tabel jenis jalan berdasarkan jenis desa.<br />
Misalkan telah ditetapkan jenis suatu desa. Untuk menghindari ada jalan berjenis 0, maka pilihan jenis desa tetangganya dapat ditentukan:<br />
<ul><li>Untuk A: B, X</li>
<li>Untuk B: A, C</li>
<li>Untuk C: B, X</li>
<li>Untuk X: A, C</li>
</ul><div>Ternyata tidak ada bedanya kalau desa ini adalah A/C, atau B/X. Dengan demikian, tabelnya dapat disederhanakan:</div><pre> A/C B/X
A/C 0 >0
B/X >0 0</pre><br />
Sekarang jadi jelas, apabila suatu desa memiliki jenis A/C, maka seluruh tetangganya wajib memiliki jenis B/X. Tidak boleh ada dua desa yang sama-sama A/C, atau sama-sama B/X. Observasi ini mengingatkan pada masalah klasik, yaitu <i>bicoloring</i>.<br />
<br />
Salah satu permasalahan <i>bicoloring</i> adalah: diberikan sebuah graf, tentukan apakah graf ini dapat diwarnai dengan dua warna, sehingga tidak ada node bertetangga yang warnanya sama. Graf yang memenuhi sifat ini dapat disebut <i>bicolorable.</i><br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3VxNZY8FwleqY3WHeey7pjB9WT46EudTNj-wBYn8rMbkO0NbA7dzJYXNAB8rIkaWeAfzN72tB-sTtgvyAbNFFUwFU7AOWlgjWl30ZxsRA-0yk-fWrGy1oXZtWdJ7bwgVAr_lUaDQtBfkh/s1600/g9051.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="270" data-original-width="500" height="172" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3VxNZY8FwleqY3WHeey7pjB9WT46EudTNj-wBYn8rMbkO0NbA7dzJYXNAB8rIkaWeAfzN72tB-sTtgvyAbNFFUwFU7AOWlgjWl30ZxsRA-0yk-fWrGy1oXZtWdJ7bwgVAr_lUaDQtBfkh/s320/g9051.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Graf dengan 2 <i>connected component </i>yang masing-masing <i>bicolorable</i>.</td></tr>
</tbody></table><div class="separator" style="clear: both; text-align: center;"></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAoSwLMvVRtpmfKnFoTnAZhOvz66UcQL2qJThSstn3Nqw17SlGF_DB3MT2_4bFvAhc84vo4YVD7Uk65Xgu0i1KfiGYaNDLl-o4cviwwkRiOn7oDca708My3lz6SGsbUhMw2JbZeeJfgV1c/s1600/g9040.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="240" data-original-width="296" height="162" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAoSwLMvVRtpmfKnFoTnAZhOvz66UcQL2qJThSstn3Nqw17SlGF_DB3MT2_4bFvAhc84vo4YVD7Uk65Xgu0i1KfiGYaNDLl-o4cviwwkRiOn7oDca708My3lz6SGsbUhMw2JbZeeJfgV1c/s200/g9040.png" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Contoh graf yang tidak <i>bicolorable</i>. Bagaimanapun pewarnaan yang dilakukan,<br />
tidak mungkin setiap <i>node </i>bertetanggaan dapat memiliki warna berbeda.</td></tr>
</tbody></table>Solusi dari <i>graph bicoloring </i>seperti ini adalah <i>greedy</i>:<br />
<ol><li>Mulai dari sembarang <i>node </i>yang belum diwarnai, warnai <i>node </i>tersebut dengan warna 1.</li>
<li>Kunjungi seluruh tetangganya yang belum diwarnai. Mereka harus diberi warna lainnya. Ulangi proses ini sampai seluruh <i>node </i>yang dapat dikunjungi telah diwarnai.</li>
<li>Apabila ada tetangga yang warnanya sama dengan warna <i>node </i>yang sedang dikunjungi, berarti komponen graf ini tidak<i> bicolorable</i>.</li>
</ol><div>Prosedur tersebut perlu dilakukan sampai seluruh <i>node </i>pada graf diwarnai. Waspada bahwa mungkin saja terdapat beberapa <i>connected component</i> pada graf yang diberikan. </div><div><br />
</div><div>Kembali ke soal JOINTS, kita diminta menghitung banyaknya cara melabeli (=mewarnai) seluruh <i>node</i>. Anggap saja warna 1 terdiri dari jenis A dan C, lalu warna 2 terdiri dari jenis B dan X. Pertama, gunakan strategi <i>greedy </i>yang dijelaskan sebelumnya untuk memeriksa sifat <i>bicolorable </i>sambil menghitung banyaknya <i>node</i> pada setiap komponen. </div><div><br />
</div><div>Ketika suatu <i>connected</i> <i>component </i>pada graf yang memiliki \(m\) <i>node </i>bersifat <i>bicolorable, </i>berarti banyaknya cara melabeli seluruh <i>node </i>pada komponen tersebut adalah \(2^m \cdot 2\). Sebab setiap <i>node </i>memiliki dua pilihan label, entah A/C atau B/X, serta kita boleh membalik kedua warna tersebut.</div><div><br />
</div>Jadi solusi akhirnya adalah kalikan seluruh kemungkinan melabeli masing-masing <i>connected component</i>. Jika suatu <i>connected component </i>tidak <i>bicolorable</i>, berarti banyaknya kemungkinan melabelinya 0. Kompleksitasnya \(O(V+E)\) dengan BFS/DFS.<br />
<br />
<br />
<hr /><h3>Koleksi Filem Alamsyah</h3>Ini soal termudah dari set soal ini. Cukup perlu dikuasai teknik membaca/memanipulasi string dan membuat <i>compare function</i> pada <i>sorting. </i>Karena malas, solusi yang saya gunakan adalah dengan pair pada STL C++. Kodenya tidak sedap dipandang tapi singkat untuk ditulis.<br />
<br />
<hr /><h3>Komentar</h3>Tingkat kesulitan set soal ini jauh lebih tinggi dari semifinal. Kelihatannya soal Kelas Matematika dan Smart Village menjadi <i>decider problem </i>untuk kontes ini. Tim yang mampu menyelesaikannyalah yang menjadi pemenang.<br />
<br />
Seluruh kode solusi dapat diunduh di <a href="https://drive.google.com/open?id=1Mf-wBXePwf8e3o45XvTeAAlxVY8ibWt7">sini</a>.William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-49339272897221890532020-04-30T09:56:00.002+07:002020-05-10T13:13:53.939+07:00Pembahasan Semifinal JOINTS PCS 2019<div style="display: none;">
</div>
Soalnya dapat Anda cek di <a href="https://tlx.toki.id/problems/joints-2019-pcs-semifinal">https://tlx.toki.id/problems/joints-2019-pcs-semifinal</a>.<br />
Seperti biasa, coba kerjakan sendiri dulu sebelum membaca tulisan ini.<br />
<br />
<hr />
<h3>
Coin Exchange</h3>
Maksud soal ini adalah mencari nilai terkecil dan terbesar yang berada di antara 1 dan N (eksklusif), sehingga nilai tersebut saling prima dengan N. Sebenarnya kondisi ini tidak menjamin seluruh nilai tukar bisa direpresentasikan. Misalnya untuk N=100, nilai terkecil yang saling prima dengannya adalah 3. Meskipun demikian, kita tidak bisa menggunakan 100 dan 3 untuk transaksi senilai 1, 2, 4, 5, 7, 8, dsb.<br />
<br />
Untuk mencari bilangan yang saling prima, cukup periksa dari 2 sampai N-1 untuk bilangan yang FPB-nya dengan N sama dengan 1. Begitu ditemukan, langsung hentikan pencarian. Nilai inilah yang menjadi nilai terkecil. Lakukan cara serupa dengan pencarian dari N-1 sampai 2 untuk mencari nilai terbesarnya. Terakhir, cetak selisih mereka.<br />
<br />
Solusi ini terkesan \(O(N)\), tetapi sebenarnya jauh lebih cepat dari itu. Sebab sangat besar kemungkinan \(N-k\) untuk \(k\) yang cukup kecil mengakibatkan FPB-nya dengan N sama dengan 1. Sebagai contoh, nilai 99 sudah saling prima dengan N=100. Demikian pula 1000000 dan 999999.<br />
<br />
<hr />
<h3>
Hitung Kata</h3>
<div>
Ini soal DP yang cukup sederhana. Idenya seperti <i>longest common subsequence</i> (LCS), tetapi kali ini seluruh karakter di string kedua wajib dicocokkan. Apabila berhasil dicocokkan, tambahkan kemungkinan jawaban dengan 1.<br />
<br />
Misalkan kedua string memiliki indeks yang dimulai dari 1. Seperti pada LCS, kita proses karakter demi karakter dari belakang.<br />
Jika \(f(i, j)\) menyatakan banyaknya subsequence dari \(S[1..i]\) yang sama dengan \(P[1..j]\), maka pilihannya adalah:<br />
<ul>
<li>Buang \(S[j]\), sehingga banyaknya cara pencocokan menjadi \(f(i-1, j)\).</li>
<li>Cocokkan \(S[i]\) dengan \(P[j]\), sehingga banyaknya cara pencocokan mejadi \(f(i-1, j-1)\). Pilihan ini hanya tersedia kalau \(S[i] = P[j]\).</li>
</ul>
<div>
Base case dicapai apabila:<br />
<ul>
<li>\(j\) bernilai 0, yang artinya pencocokan berhasil.</li>
<li>\(i\) bernilai 0, tetapi \(j\) bukan 0, yang artinya pencocokan tidak berhasil.</li>
</ul>
</div>
Secara matematis, bisa dituliskan:<br />
\[ f(i,j) = \left\{\begin{array}{ll}<br />
1 & ,j = 0\\<br />
0 & ,i=0 \land j>0\\<br />
f(i-1,j) + f(i-1,j-1) & ,S[i]=P[j]\\<br />
f(i-1,j) & ,S[i] \ne P[j]\\<br />
\end{array}\right. \]<br />
Sebelum mengimplementasikannya, saya menyadari kalau tidak ada modulo. Lalu saya bertanya-tanya apakah jawabannya muat ditampung dalam integer 64 bit. Jawaban terbesar dicapai apabila \(S\) berisi 'A' sebanyak 1000 karakter, dan \(P\) berisi 'A' pula sebanyak 10 karakter. Jawabannya adalah 1000 kombinasi 10, yang mana melebihi batas atas integer 64 bit.<br />
<br />
Karena malas menggunakan big integer, saya coba submit saja. Ternyata accepted :))<br />
Kompleksitas solusi ini \(O(|S||P|)\).<br />
<br />
<hr />
<h3>
Kerja Pak Blangkon</h3>
</div>
<div>
Observasi yang penting adalah setiap pekerjaan dapat diselesaikan dalam 1 hari, sehingga pekerjaan yang lebih menguntungkan pasti dipilih terlebih dahulu.<br />
<br />
<a name='more'></a><br />
Observasi kedua, suatu pekerjaan dipilih pasti lebih baik kalau pengerjaannya ditunda selama mungkin (kalau bisa dikerjakan saat deadline saja). Strategi ini memaksimalkan kemungkinan pengerjaan kerjaan lain deadline-nya lebih ketat.<br />
<br />
Dari kedua observasi tersebut, kita bisa menggunakan algoritma greedy sebagai berikut:<br />
<ol>
<li>Urutkan semua pekerjaan dari yang penghasilannya terbesar.</li>
<li>Periksa pekerjaan satu per satu dari yang penghasilannya terbesar, lalu rencanakan pengerjaannya sedekat mungkin dengan deadline (kalau bisa saat deadline). Kalau ada hari yang mungkin, berarti pilih pekerjaan ini.</li>
</ol>
<br />
Untuk mencari hari terdekat dengan deadline, dibutuhkan struktur data yang menyimpan sejumlah elemen dan mendukung operasi berikut secara efisien:<br />
<ol>
<li>Tandai elemen ke-x sebagai terhapus.</li>
<li>Cari elemen ke-x. Apabila elemen ke-x telah terhapus, cari elemen terbesar yang kurang dari x dan belum terhapus.</li>
</ol>
<div>
Ini seperti tugas yang cocok untuk linked list. Namun perhatikan bahwa elemennya tidak pernah dibuang, dan hanya ditandai sebagai terhapus. Kalau menggunakan linked list, operasi kedua bisa jadi tidak O(1). Perhatikan gambar berikut:<br />
<br /></div>
<div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG_fLGE-47MZK03ym1ODqQNpIZ3KmIo-3zHDeKwphVrpk7ylxrkmYQFpeStlHXRwgzmZvBxN5BIKXgT3_8lx6svf9XWvhmN8wYLKqYrxnmxT13okjfhTP8VUDtJCEAcYIxRRiejyMe1ZqG/s1600/g4469.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="361" data-original-width="500" height="231" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG_fLGE-47MZK03ym1ODqQNpIZ3KmIo-3zHDeKwphVrpk7ylxrkmYQFpeStlHXRwgzmZvBxN5BIKXgT3_8lx6svf9XWvhmN8wYLKqYrxnmxT13okjfhTP8VUDtJCEAcYIxRRiejyMe1ZqG/s320/g4469.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Setelah elemen ke-4 dan ke-3 dihapus, operasi pencarian elemen<br />
yang belum dihapus sebelum elemen ke-4 menjadi tidak O(1).</td></tr>
</tbody></table>
<br />
Masalah ini dapat diatasi dengan <i>path compression</i> seperti pada struktur data disjoint set. Lalu kenapa kita tidak menggunakan disjoint set saja?</div>
<div>
<br /></div>
<div>
Kedua operasi itu dapat dilayani dengan sempurna dengan disjoint set.</div>
<div>
Awalnya setiap elemen menunjuk ke dirinya sendiri. Operasi pertama dilakukan dengan menggabungkan elemen ke-x dengan kelompok elemen ke-(x-1). Penggabungan ini wajib dilakukan dengan mengganti parent dari x menjadi ujung dari parent elemen ke-(x-1).<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRKRStSzxGCGAisRo8KYYb6f5bXNpuIgTAtpYCQ8zF1lzI_AAtk5t2yft6qi7gpsls91Egyse1Vtm0u0Ix-lE8TwYtnLMoVfCojODJkzsr8DjD0PW3K18Gv-4wx2yH2db34rQUXzewtaSO/s1600/g5977.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="588" data-original-width="499" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRKRStSzxGCGAisRo8KYYb6f5bXNpuIgTAtpYCQ8zF1lzI_AAtk5t2yft6qi7gpsls91Egyse1Vtm0u0Ix-lE8TwYtnLMoVfCojODJkzsr8DjD0PW3K18Gv-4wx2yH2db34rQUXzewtaSO/s320/g5977.png" width="271" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Setelah penghapusan elemen ke-4 dan ke-3, penghapusan elemen ke-5 dilakukan dengan mengganti parent-nya menjadi parent dari elemen ke-4. Pencarian parent elemen ke-4 akan memicu <i>path compression </i>sehingga dia langsung menunjuk ke elemen ke-2, sehingga operasi yang melibatkan pencarian parent elemen ke-4 di masa depan efisien.</td></tr>
</tbody></table>
</div>
<div>
Strategi ini menjamin ujung dari parent elemen ke-x selalu menunjuk ke elemen sebelumnya yang belum terhapus. Sehingga untuk melayani operasi kedua, cukup cari ujung dari parent x. Teknik ini saya pelajar di Pelatnas 2 TOKI 2011.</div>
<div>
<br /></div>
<div>
Solusi ini dapat diimplementasikan dalam \(O(N \log{N})\) dengan pengurutan dan disjoint set.</div>
<br />
<hr />
<h3>
Smart City</h3>
</div>
<div>
Nilai terbesar \(N\) pada soal ini adalah 100. Jadi bisa saja kalau kita coba semua kemungkinan pasangan titik, dan periksa apakah jalurnya mengandung dua titik rusak. Kompleksitasnya \(O(N^3)\), dan cukup untuk accepted.<br />
<br />
Optimisasi sederhana adalah mencoba semua kemungkinan titik awal, lalu DFS ke seluruh titik sambil menghitung titik akhir mana saja yang bisa dicapai. Kompleksitasnya \(O(N^2)\). Ini adalah solusi yang saya gunakan.<br />
<br />
<hr />
<h3>
Your Classical OSP Problem</h3>
</div>
<div>
Berhubung pergerakannya hanya bisa ke atas atau kanan, maka setiap petak yang dimaui pasti dikunjungi dengan suatu urutan tertentu. Yang memiliki jarak manhattan terdekat dengan \((1, 1)\) akan dikunjungi pertama, disusul dengan yang terdekat kedua, dan seterusnya. Secara umum, kita dapat mengurutkan seluruh petak dimaui berdasarkan nilai baris ditambah kolomnya.<br />
<br />
Dengan observasi tersebut, kita dapat menggunakan DP yang umum untuk soal ini secara bertahap. Kalau kalian sudah tahu apa DP yang saya maksud, silakan lompat ke paragraf selanjutnya. Misalkan \(f(i,j)\) menyatakan banyaknya cara dari \((1, 1)\) sampai \((i, j)\). Untuk mencapai \((i, j)\), kita pasti berasa dari petak di bawahnya atau di kirinya. Jadi banyaknya cara sama saja dengan penjumlahan dari banyaknya cara ke \((i-1, j)\) dan banyaknya cara ke \((i, j-1)\). Base case terjadi saat \(i=1\) dan \(j=1\), yang mana ada 1 cara untuk mencapainya. Khusus soal ini, kalau petak \((i, j)\) merupakan jurang, berarti \(f(i, j)=0\). Nilai \(f(i, j)\) juga nol apabila \(i\) atau \(j\) keluar peta. Secara matematis dirumuskan:<br />
\[ f(i,j) = \left\{\begin{array}{ll}<br />
1 & ,i=1 \land j = 1\\<br />
0 & , i < 1 \lor j < 1 \\<br />
0 & , (i, j) \in Jurang \\<br />
f(i-1,j) + f(i,j-1) & ,(i, j) \notin Jurang\\<br />
\end{array}\right. \]<br />
Misalkan petak yang dimaui ke-\(i\) berada di \((r_i, c_i)\). Mulailah dengan mencari banyaknya cara dari \((1, 1)\) ke \((r_1, c_1)\) dengan DP biasa. Selanjutnya, cari banyaknya cara dari \((r_1, c_1)\) ke \((r_2, c_2)\) dengan cara yang sama, dan seterusnya sampai petak dimaui ke-\(Q\). Terakhir, lakukan hal serupa dari \((r_Q, c_Q)\) ke \((N, M)\).<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvBXGzgVhuEt0u7JlNPFzpUWiQ0etqKPCot2hChyRK2K81Xqxfi9nGchWsGyjHj214hrBrmbJk6DiPnufH09BNAs9oBB-Ub9__VD3mNAar57Vhd6ZTCbMIeG_Qg6FyP0ImA6WhOqA-NXAb/s1600/rect6550.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="1600" data-original-width="497" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvBXGzgVhuEt0u7JlNPFzpUWiQ0etqKPCot2hChyRK2K81Xqxfi9nGchWsGyjHj214hrBrmbJk6DiPnufH09BNAs9oBB-Ub9__VD3mNAar57Vhd6ZTCbMIeG_Qg6FyP0ImA6WhOqA-NXAb/s640/rect6550.png" width="198" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Eksekusi DP secara bertahap sesuai dengan banyaknya peta yang dimaui.<br />
Petak hitam menyatakan jurang, dan petak merah menyatakan petak dimaui.</td></tr>
</tbody></table>
Setiap DP hanya boleh memedulikan nilai yang ada di antara ujung kiri bawah dan ujung kanan atasnya. Hati-hati untuk tidak menggunakan nilai dari DP tahap sebelumnya.<br />
Secara kolektif, solusi ini bekerja dalam \(O(NM)\).<br />
<br />
Ada sedikit jebakan di soal ini, yaitu daftar petak yang dimaui bisa tidak unik. Jadi hati-hati kalau solusi Anda bergantung pada nilai Q.<br />
<br />
<hr />
<h3>
Komentar</h3>
</div>
<div>
Rasanya batasan untuk soal-soalnya dapat ditingkatkan, supaya hanya solusi yang efisien bisa accepted. Misalnya untuk soal Smart City, bisa saja dibuat nilai terbesar N=1000 supaya solusi \(O(N^3)\) tidak accepted. Demikian pula untuk soal Your Classical OSP Problem.<br />
<br />
Secara umum soalnya menarik, dan komentar saya mirip seperti pada soal penyisihan.<br />
Seluruh kode solusi dapat diunduh di <a href="https://drive.google.com/open?id=1JJOxo4ov1xRUTSXCqaO6QyS-e63F9hDt">sini</a>.</div>
<br />William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-68697027645661949302020-04-27T19:30:00.001+07:002021-04-12T07:36:51.112+07:00Pembahasan Penyisihan JOINTS PCS 2019<div style="display: none;"></div>Sempat ada laporan kalau ada masalah konfigurasi di soal-soal JOINTS 2019 yang dipasang di TLX. Sambil diperbaiki satu per satu, saya sekalian membaca soalnya. Berhubung soal-soalnya menarik, jadinya saya kerjakan dan akan saya bahas di sini.<br />
<br />
Soalnya dapat Anda temui di web TLX: <a href="https://tlx.toki.id/problems/joints-2019-pcs-penyisihan">https://tlx.toki.id/problems/joints-2019-pcs-penyisihan</a><br />
<br />
Sangat dianjurkan bagi Anda untuk mencobanya terlebih dahulu. Kalau tersendat baru liat pembahasan di sini :-)<br />
<br />
Kode solusi saya cantumkan di akhir tulisan.<br />
<br />
<hr /><h3>Dolan 2019</h3>Deskripsi soal inilah yang membuat saya tertarik mengerjakan seluruh set soal.<br />
<br />
Cukup proses setiap barisnya satu per satu sambil mencatat kategori jawaban terbaik sejauh ini. Supaya mudah, kodekan kategori jawaban sebagai berikut:<br />
0. BALIK AJA<br />
1. TERPAKSA<br />
2. SENANG<br />
<br />
Awalnya inisialisasi kategori jawaban dengan 0 (untuk "BALIK AJA"). Untuk setiap baris, lakukan iterasi dari kiri ke kanan sambil mencatat:<br />
<ol><li>Karakter terakhir sebelum 'O'. Simpan karakter ini di variabel "prev".</li>
<li>Banyaknya karakter 'O' konsekutif sampai sejauh ini. Anggap banyaknya ini disimpan di variabel "count".</li>
</ol><div>Setiap kali ditemukan karakter yang bukan 'O', simpan karakter itu di variabel "next" dan periksa apakah:<br />
<ol><li>count ≥ N+2. Kalau iya, berarti geng CJ pasti bisa "SENANG".</li>
<li>count ≥ N+1. Kalau iya, periksa apakah salah satu dari prev atau next bernilai 'L'. Kalau iya berarti mereka "SENANG", dan kalau tidak berarti kategori 1 menjadi kandidat jawaban.</li>
<li>count = N dan nilai dari prev dan next sama-sama 'L'. Kalau iya berarti "SENANG", dan kalau tidak berarti kategori 1 menjadi kandidat jawaban.</li>
</ol>Jangan lupa melakukan pemeriksaan kalau sudah mencapai ujung kanan kolom dan karakternya masih juga 'O'. Asumsikan saja next bernilai 'L'.<br />
<br />
</div>Terakhir, tinggal cetak kategori jawaban terbaik. Kompleksitas solusinya \(O(RC)\).<br />
<br />
<hr /><h3>KPK</h3>Solusi brutal jelas akan mendapat TLE, berhubung \(3^N\) dengan \(N=10^6\) terlalu besar.<br />
<br />
Pertama, coba kita cari KPK dari suatu bilangan \(x\) dengan \(3^N\). Kita dapat menyatakan \(x\) sebagai perkalian dari suatu bilangan \(k\) dengan \(3^p\), sedemikian sehingga \(k\) tidak lagi mengandung faktor 3:<br />
\[x = k 3^p\]<br />
Mengingat masa SD, KPK dari \(k 3^p\) dan \(3^N\) adalah \(k 3^N\).<br />
Bila \(x\) dimulai dari 1 sampai dengan \(3^N\), maka nilai yang ingin dicari adalah:<br />
\[ \begin{array}{c} k_1 3^N + k_2 3^N + k_3 3^N + ... + k_{3^N} 3^N <br />
\\ = 3^N (k_1 + k_2 + k_3 + ... + k_{3^N} ) \end{array} \]<br />
<a name='more'></a>Dengan \(k_x\) menyatakan perkalian seluruh faktor \(x\) yang bukan 3.<br />
<br />
Kini kita perlu mencari jumlahan barisan \(k_x\). Saya akan menyebut barisan ini dengan nama "barisan ampas" karena setiap elemennya seperti sisa bilangan setelah seluruh faktor 3-nya diperas habis.<br />
<br />
Beberapa nilai pertama barisan ampas adalah:<br />
<pre>1 2 <u>1</u> 4 5 <u>2</u> 7 8 <u>1</u> 10 11 <u>4</u> 13 14 <u>5</u> ...
</pre>Perhatikan bilangan yang tidak digaris bawah. Elemen di posisi bukan kelipatan 3 dijamin tidak berubah, karena mereka tidak mengandung faktor 3. Jumlahan elemen-elemen tersebut dapat dinyatakan dengan jumlah bilangan dari 1 sampai \(3^N\), dikurangi dengan jumlah elemen di posisi kelipatan 3:<br />
\[ \sum_{i=1}^{3^N} i - 3\sum_{i=1}^{3^{N-1}} i \]<br />
Sekarang kita tinggal menambahkannya dengan jumlahan bilangan-bilangan di posisi kelipatan 3. Coba kita kumpulkan nilainya dulu:<br />
<pre> <u>1</u> <u>2</u> <u>1</u> <u>4</u> <u>5</u> ...</pre>Ternyata ada pola yang berulang, nilai tersebut sama persis dengan barisan ampas. Perbedaannya hanya banyaknya elemen barisan ini adalah sepertiga sebelumnya, yakni \(3^{N-1}\). Ini bukan kebetulan semata karena pembagian dari barisan 3, 6, 9, 12, 15, ... dengan 3 akan memunculkan 1, 2, 3, 4, 5, ...<br />
Hal ini memberikan ide penyelesaian rekursif.<br />
<br />
Apabila kita definisikan \(f(N)\) sebagai jumlahan barisan ampas pada awalnya, kini dapat dirumuskan:<br />
\[f(N) = \sum_{i=1}^{3^N} i - 3\sum_{i=1}^{3^{N-1}} i + f(N-1)\]<br />
Tetapkan base case-nya saat \(N=0\), yang mana \(f(0)=1\).<br />
<br />
Untuk menghitung jumlah dari 1 sampai \(3^N\) secara cepat, gunakan rumus deret aritmetika:<br />
\[\sum_{i=1}^{3^N} i = \frac{3^N(3^N+1)}{2}\]<br />
Sayangnya rumus ini tidak langsung dapat digunakan dengan modulo, karena ada pembagian dengan 2. Berhubung nilai modulonya dan 2 saling prima, kita dapat menggunakan invers modulo. Singkat cerita, perhitungannya menjadi:<br />
\[\sum_{i=1}^{3^N} i \bmod M= 3^N(3^N+1)2^{M-2} \bmod M\]<br />
(jangan lupa untuk mencari \(2^{M-2}\) dengan pemangkatan fast exponentiation).<br />
<br />
Sekarang tinggal menghitung nilai \(f\) dari 0 sampai dengan N seperti DP bottom up.<br />
Setiap pertanyaan kini dapat dijawab dengan mencari \(3^N f(N)\). Solusi ini bisa diimplementasikan dalam \(O(N)\).<br />
<br />
<hr /><h3>Perpustakaan Blangkon</h3>Soal sederhana kalau sudah tahu struktur data hash table atau BST. Cukup pakai unordered_map atau map pada C++. Kompleksitas solusi akhirnya \(O(N+M)\), dengan asumsi panjang judul buku tidaklah signifikan.<br />
<br />
<hr /><h3>Sepotong Batik</h3>Bagian yang merepotkan dari soal ini adalah pola mungkin terpotong di atas, kiri, bawah, atau kanan.<br />
<br />
Untuk sedikit meringankan kerepotan, saya akan menambah seluruh koordinat dengan satu.<br />
Lalu gunakan strategi 2D partial sum. Misalkan \(f(i, j)\) menyatakan banyaknya titik warna utama dari posisi \((1, 1)\) sampai \((i, j)\). Mencari banyaknya titik warna dari \((r_1, c_1)\) sampai dengan \((r_2, c_2)\) sama dengan mencari:<br />
\[f(r_2, c_2) - f(r_2, c_1-1) - f(r_1-1, c_2) + f(r_1-1, c_1-1)\]<br />
Perhatikan ilustrasi berikut untuk jelasnya:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijRT_gaAVtd6ozRJ1rKCj2b0-kOltjV77fbHtjruDtAomxIsIfVWnZ9K2wsfqyi-l1JYme8qIeH0BEpltDgEttcqEBKvafpGCbgUKiQVJbTPZFEB0x0Vr8Emr06y1lqpiw4T2tqWG2t4Cm/s1600/rect6550.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="500" data-original-width="500" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijRT_gaAVtd6ozRJ1rKCj2b0-kOltjV77fbHtjruDtAomxIsIfVWnZ9K2wsfqyi-l1JYme8qIeH0BEpltDgEttcqEBKvafpGCbgUKiQVJbTPZFEB0x0Vr8Emr06y1lqpiw4T2tqWG2t4Cm/s400/rect6550.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: 12.8px;">Pencarian jumlahan elemen di (r</span><sub>1</sub><span style="font-size: 12.8px;">, c</span><sub>1</sub><span style="font-size: 12.8px;">) sampai (r</span><sub>2</sub><span style="font-size: 12.8px;">, c</span><sub>2</sub><span style="font-size: 12.8px;">) dengan 2D partial sum.</span></td></tr>
</tbody></table>Karena sekarang koordinat kiri atas selalu \((1, 1)\), kita hanya perlu khawatir dengan pola-pola yang terpotong di bagian bawah atau kanan.<br />
<br />
Pencarian banyaknya titik warna dapat dipecah menjadi beberapa langkah:<br />
<ol><li>Buang semua jarak antar pola (vertikal maupun horizontal). Misalkan kini banyaknya baris dan kolom adalah rClean dan cClean.</li>
<li>Hitung banyaknya baris yang sepenuhnya mengandung pola (tanpa peduli apakah ada pola terpotong di kanannya).</li>
<li>Lakukan iterasi i=1 sampai dengan A, dan hitung banyaknya titik warna di posisi (i, 1) sampai dengan (i, cClean). Kalikan jawabannya dengan banyaknya baris yang sepenuhnya mengandung pola.</li>
<li>Untuk pola yang terpotong di bawah, iterasi i=1 sampai dengan rClean%A, dan hitung banyaknya titik warna di posisi (i, 1) sampai dengan (i, cClean).</li>
</ol>Pencarian titik warna dari posisi (i, 1) sampai dengan (i, cClean) pun dapat menggunakan strategi yang serupa. Yang penting Anda hati-hati dalam menghitung indeksnya.<br />
<br />
Dengan cara ini, fungsi \(f\) dapat dihitung dalam \(O(A)\).<br />
Sangat cukup untuk mendapatkan accepted.<br />
<br />
<hr /><h3>Komentar</h3>Soal favorit saya adalah KPK, dan saya rasa ada solusinya yang lebih mudah. Kalau Anda tahu, mohon beritahu saya.<br />
<br />
Sentuhan akhir yang saya sarankan adalah penyeragaman format soal. Pada set soal ini, ada soal formatnya multicase dan menggunakan prefix "Kasus #x: ...", sementara soal lainnya tidak. Meskipun rasanya kurang penting untuk menyamakan formatnya, menurut saya ini akan memberikan kesan lebih artistik.<br />
<br />
Seluruh kode solusi dapat diunduh di <a href="https://drive.google.com/open?id=19dJinQbdpc_ebyCmTMpo2v2-ArSrZdDV">sini</a>.<br />
Saya akan membahas pembahasan semifinal pada tulisan mendatang.William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com2tag:blogger.com,1999:blog-2894426456296176040.post-27930877116793188702019-12-20T15:43:00.000+07:002020-04-19T09:52:31.065+07:00ICPC 4 Tahun<div style="display: none;">
<img border="0" data-original-height="640" data-original-width="960" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinzIKeVtcOiNtY8AWddbf4ZR8NA2SPIOfsRhewjeyeteYhGhIHq6WHEO3i7zAlECEOSANC1J-v60kZK9R9NSMAy6H9UCRFihDWkRAu4YNxeNzFYa_dWreTYO8MgHxqNRkLtoaijZcTVZYp/s400/jakarta-2013-3-pd.jpg" width="400" /></div>
Setelah menceritakan pengalaman <a href="https://kupaskode.blogspot.com/2013/07/kisah-perjalanan-di-toki-saya-osk-dan.html">menuju OSN/IOI</a>, kini saya akan menceritakan pengalaman menuju ICPC World Final. Tujuannya adalah berbagi pengalaman. Berhubung rentang maksimal untuk karir ICPC adalah 4 tahun, yang lebih lama dari 2-3 tahun pada OSN/IOI, memutuskan untuk menginvestasikan sebagian dari 4 tahun kuliah ke ICPC merupakan komitmen yang panjang.<br />
<br />
<hr />
<h3>
Partisipasi Pertama</h3>
Setelah masuk Fasilkom UI dan mengikuti berbagai orientasi, saya direkrut untuk ikut ICPC oleh Felik. Anggota tim satunya lagi adalah Rasmunandar Rustam, sehingga tim kami sepenuhnya adalah TOKI 2011. Nama tim yang disepakati adalah Vidina, dari Felik yang artinya "masa depan".<br />
<br />
Sebenarnya saya hanya tahu kalau ICPC adalah kontes pemrograman seperti IOI, dengan ciri:<br />
<ul>
<li>Partisipasi tim</li>
<li>Penilaian jawaban hanya ada benar atau salah</li>
</ul>
<div>
... dan saya tidak tahu bagaimana aturannya kualifikasinya bekerja. Apakah dipilih N tim terbaik di suatu negara? Atau suatu region? Bagaimana pembagian regionalnya bekerja? Berhubung baru mulai kuliah dan banyak aktivitas, saya tidak mencari tahu jawabannya (atau mungkin karena saya lupa).</div>
<div>
<br /></div>
<div>
Felik kemudian mengurus administrasi tim kami. Kami juga berkenalan dengan Pak Denny, yang merupakan coach untuk tim UI untuk urusan ICPC. Saya mempelajari bahwa biasanya UI mengirim sekitar 3 tim ke suatu regional di Asia. Kalau pada tahun itu diselenggarakan ICPC regional Jakarta juga, maka UI dapat mengirim segerombolan tim karena biaya yang jauh lebih murah. Sebagai catatan, ICPC hanya ada pada semester ganjil.</div>
<div>
<br /></div>
<div>
Berhubung tahun 2011 tidak ada ICPC regional Jakarta, berarti tahun ini hanya 3 tim yang akan ikut regional. Penentuan 3 tim ini diputuskan dengan INC (Indonesia National Contest) yang diselenggarakan Binus. Tim Vidina berhasil mengamankan posisi di INC sebagai salah satu dari 3 tim terbaik dari UI, sehingga kami akan dikirim ke ICPC regional.</div>
<div>
<br /></div>
<div>
Ternyata regional yang dipilih adalah Kuala Lumpur. Kami berangkat ke Malaysia berasama dengan Pak Denny dan 1 tim UI lainnya. Kontesnya akan diselenggarakan di IIUM (International Islamic University Malaysia).</div>
<div>
<br /></div>
<div>
Selama perjalanan, saya belajar dari Pak Denny kalau sekitar 3 tim terbaik di suatu regional akan terkualifikasi ke ICPC World Final (biasa disebut "WF" saja). Aturan sebenarnya lebih rumit, yang melibatkan regional quota, suatu rumus yang memusingkan, pertimbangan khusus, dan sebagainya. Rasanya yang penting bertanding sebaik mungkin, lalu serahkan WF atau tidak ke tangan panitia.</div>
<div>
<br /></div>
<div>
Sejujurnya saya tidak banyak persiapan untuk ICPC ini. Saya juga tak tahu apa harapan partisipasi ini. Rasanya untuk menduduki 3 besar tidaklah mungkin. Namun karena sudah jauh-jauh ke Malaysia, saya tidak pikir panjang dan berkompetisi sebaik mungkin.</div>
<div>
<br /></div>
<div>
Hasil kompetisinya tidak baik. Peringkat akhir Vidina adalah 25-an, yang sangatlah jauh dari 3 besar.</div>
<br />
Rasanya tidak enak telah menghabiskan uang fakultas, lalu tidak berkompetisi dengan baik. Kami pulang ke Indonesia sambil memikirkan solusi soal kompetisinya.<br />
<br />
Pada semester kedua, Rasmunandar mundur dari Vidina karena ingin fokus ke pemrograman yang lebih aplikatif (seperti website atau aplikasi HP). Felik kemudian merekrut Cakra, yang tahun depan akan masuk UI sebagai angkatan 2012, dan nama timnya menjadi Vidina 2.0.<br />
<br />
<br />
<hr />
<h3>
Tahun Kedua yang Suram</h3>
Masuk semester ketiga, tim kami kembali mengikuti INC dan masuk ke dalam 3 tim terbaik UI. Tahun ini terdapat ICPC regional Jakarta, sehingga kami akan berpartisipasi di 2 kontes. Ternyata selain Jakarta, kami akan dikirim ke Hanoi. Sebagai catatan, kontes di Hanoi jauh lebih awal daripada Jakarta.<br />
<br />
Hasil regional di Hanoi suram, lagi-lagi kami peringkat 25-an. Namun kali ini saya cukup sedih karena tidak ada kemajuan jika dibandingkan tahun sebelumnya, padahal sudah berlatih selama semester genap. Selain merasa bersalah kepada Pak Denny dan fakultas karena tidak memberikan hasil yang baik, saya mulai merasa untuk mendapat peringkat 3 besar tidaklah mungkin. Saingan dari Taiwan, Cina, Hongkong, Vietnam, atau Singapura (yang isinya teman-teman TOKI juga) terlalu tangguh untuk dikalahkan. Rasa suram ini membuat saya kehilangan semangat dan tidak ikut jalan-jalan ke Ha Long Bay. Setidaknya, pengalaman berkunjung Vietnam cukup menarik dan saya membeli caping khas sana.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3S9NcQqigcQWkSgTvbYOBM3O73SIDp14-MVCKooWKuDjahdFDnQIejKTzCdwCPOcfTzB3zxSG3c6qdrgMTMm7mBrmwRYLLOdOKwQd0G1OEAdrpkGk_wjOGRS1X9k_dNG1MuFjtjatqiHW/s1600/pandaren-felik.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="960" data-original-width="720" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3S9NcQqigcQWkSgTvbYOBM3O73SIDp14-MVCKooWKuDjahdFDnQIejKTzCdwCPOcfTzB3zxSG3c6qdrgMTMm7mBrmwRYLLOdOKwQd0G1OEAdrpkGk_wjOGRS1X9k_dNG1MuFjtjatqiHW/s200/pandaren-felik.jpg" width="150" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Caping "Pandaren" (foto oleh Felik)</td></tr>
</tbody></table>
Kontes selanjutnya adalah INC 2012, yang mana Cakra tidak bisa ikut karena ada acara orientasi. Jadinya hanya saya dan Felik yang harus <i>nge-tank </i>dan <i>carry</i>. Namun secara mengejutkan, performa kami sangat baik dan memperoleh peringkat ke-3. Hasil yang lebih baik mungkin bisa dicapai kalau kami ber-3. Berkat hasil yang baik ini, moral berperang saya kembali bangkit, dan siap untuk menghadapi regional Jakarta.<br />
<br />
Tim UI berbondong-bondong berangkat ke Binus dengan bis kuning untuk regional Jakarta. Ini kedua kalinya saya ke Binus, setelah BNPC HS 2011 pada Pelatnas 1 yang lalu. Berkat moral yang positif, kami bertanding dengan baik dan berhasil duduk di peringkat 10. Ada pencapaian pertama yang diraih, yaitu <i>first solver</i> untuk suatu soal dan kami mendapatkan balon bintang.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSy-_vetd66vMoNsDBXuGKzy9Sa0iDRX5JdItE8ww7VOekMqTPTXjei8wgZmppajkQMODySN9yvrWbMr9eFqfEKlfJbsXq3E2x3XsYFPe36TMmV44JqLrhAmqAGNT2dEJnAdUQ5W4YQWJ3/s1600/jakarta-2012-felik.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="720" data-original-width="960" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSy-_vetd66vMoNsDBXuGKzy9Sa0iDRX5JdItE8ww7VOekMqTPTXjei8wgZmppajkQMODySN9yvrWbMr9eFqfEKlfJbsXq3E2x3XsYFPe36TMmV44JqLrhAmqAGNT2dEJnAdUQ5W4YQWJ3/s320/jakarta-2012-felik.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Foto dari Felik</td></tr>
</tbody></table>
Lebih jauh lagi, ternyata kami mendapat penghargaan tim lokal (Indonesia) terbaik ke-3 dan mendapat plakat. Tim lokal terbaik pertama dan ke-2 adalah "Dongskar Pedongi" (timnya Irvan Jahja) dan "+1 Saklar Lhompat" (timnya Ashar). Penghargaan ini juga tiba dengan hadiah berupa hard disk eksternal. Kebetulan saat itu saya perlu format komputer dan butuh media penyimpanan data yang besar, jadilah pucuk dicinta ulam pun tiba.<br />
<br />
Walaupun hasil ICPC di Hanoi suram, tapi hasil di Jakarta memberikan harapan. Masih ada waktu 2 tahun untuk berlatih untuk mencapai WF!<br />
<br />
<a name='more'></a><br />
<hr />
<h3>
Tahun Ketiga yang Menjanjikan</h3>
Mulai tahun ini, Pak Denny melakukan percobaan dengan menentukan komposisi tim. Tim yang dikirim ke luar negri dipilih berdasarkan suatu kontes yang diikuti secara individual. Lalu peringkat 1-3 menjadi tim 1, 4-6 menjadi tim 2, dan seterusnya. Untuk tim yang berpartisipasi di regional Jakarta (kebetulan tahun ini ada lagi), kami bebas membentuk tim.<br />
<br />
Hasil akhirnya adalah saya berada di peringkat ke-3, setelah Ashar dan Aji. Terbentuklah tim, yang dinamakan "+1 2.0". Namanya memang aneh, tapi sebenarnya itu gabungan dari "+1 Saklar Lhompat" dan "Vidina 2.0". Kami akan bertanding di Phuket setelah regional Jakarta. Untuk regional Jakarta sendiri, saya masih dalam tim Vidina 2.0.<br />
<br />
Tim UI kembali berbondong-bondong ke Binus menaiki bis kuning. Untuk kontes kali ini, tim Vidina 2.0 sangat berhati-hati. Dengan pengalaman yang terkumpul sejauh ini, kami membaca suatu soal, analisis, dan kalau sudah yakin baru menulis kode.<br />
<br />
Awal dan pertengahan kontes berlangsung dengan baik, kami berhasil AC dengan 1-shot untuk berbagai soal. Pada jam ke-5, kami mulai tersendat. Saya menemui solusi untuk soal J, yang berjudul "Alien Abduction Again", tapi terus-terusan mendapat TLE. Rasanya tidak mungkin, karena algoritmanya sudah O(N log N). Sambil Felik dan Cakra memikirkan soal lain, saya mulai <a href="https://kupaskode.blogspot.com/2014/01/teknik-optimisasi-konstanta.html">mengoptimisasi konstantanya</a> secara habis-habisan. Setelah disubmit, saya ke toilet dan minum susu ultra. Kembali dari toilet, saya dikabari Cakra kalau solusinya AC! Sisa waktunya kami gunakan untuk menghadapi soal lain, tapi tidak ada AC lagi.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJCqgQaUIDASE7K22WLu4JFrDyo_xQH180Y0BYQ_toHwr1pcx7WqZlu7PzRO_Y_VnrDwFjmT8-aCsLU234bRKSTS1JdVZSdBY8I4X9T7sSO3L32xqlMz3NvtkyojivXQOsfs1QZ5NlrLkT/s1600/jakarta-2013-2-felik.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="480" data-original-width="361" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJCqgQaUIDASE7K22WLu4JFrDyo_xQH180Y0BYQ_toHwr1pcx7WqZlu7PzRO_Y_VnrDwFjmT8-aCsLU234bRKSTS1JdVZSdBY8I4X9T7sSO3L32xqlMz3NvtkyojivXQOsfs1QZ5NlrLkT/s320/jakarta-2013-2-felik.jpg" width="240" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Akhir kontes yang berkesan (foto dari Felik)</td></tr>
</tbody></table>
Pada saat penutupan, saya terkejut karena tim kami berada di peringkat ke-6. Selain itu, kami menjadi best local team! Ini bagian yang paling mengharukan, karena untuk pertama kalinya bisa melampaui tim Saklar yang selalu saya kagumi. Selangkah lebih maju untuk WF!<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVjJAZbZf_8cIYR72JWXeJppLHLKvoAZay1SMxEyxqQJrLdCjMt9-xysE652H2ujnA-FiQ7acwxqH-kHuYssZkbzdQxFF2fM0jXT1JMFaHo3g2sFWaVVawYp-cdAqZVQSp2BAbHlRVzBd5/s1600/jakarta-2013-binus-best-national.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="640" data-original-width="960" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVjJAZbZf_8cIYR72JWXeJppLHLKvoAZay1SMxEyxqQJrLdCjMt9-xysE652H2ujnA-FiQ7acwxqH-kHuYssZkbzdQxFF2fM0jXT1JMFaHo3g2sFWaVVawYp-cdAqZVQSp2BAbHlRVzBd5/s320/jakarta-2013-binus-best-national.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Best Local Team! (foto dari panitia ICPC)</td></tr>
</tbody></table>
Secara total, tim Vidina 2.0 telah mendapatkan 3 plakat:<br />
<ol>
<li>Juara 3 INC 2012</li>
<li>Juara 3 best local team regional Jakarta 2012</li>
<li>Juara 1 best local team regional Jakarta 2013</li>
</ol>
Setelah pertandingan kami yang terakhir sebagai 1 tim, kami membagi ketiga plakat itu sehingga masing-masing memegang 1.<br />
<br />
Perjuangan belum selesai karena masih ada ICPC di Phuket. Sebelum bertanding, saya ada latihan tim bersama Ashar dan Aji untuk beradaptasi di tim yang berbeda. Kami juga melakukan "persilangan" team notebook untuk saling melengkapi bagian yang kurang.<br />
<br />
Rincian tentang pertandingan di Phuket bisa dibaca di <a href="http://fusharblog.com/acm-icpc-asia-phuket-regional-contest-2013/">blognya Ashar</a>. Intinya kontes itu seperti perang, dengan Aji sebagai "mage" (menyelesaikan soal dengan solusi ajaib), Ashar sebagai "knight" (menyelesaikan soal secara sistematis), dan saya sebagai "berserker" (menyelesaikan soal secara gegabah).<br />
<br />
Hasil akhirnya adalah kami berada di peringkat ke-6. Memang bukan 3 besar yang aman untuk ke WF, tapi Pak Denny menganalisis ada peluang kami terkualifikasi ke WF. Alasannya adalah tim di peringkat 1-5 ada yang berasal dari Cina, dan mereka tidak dapat menggunakan kuota kualifikasi WF di Phuket ini. Saya tidak paham betul tentang aturannya, jadi saya pasrahkan saja. Lalu karena performa yang memuaskan, tahun ini saya ikut untuk tur di Phuket ke "James Bond Island".<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiivNstsQV_daY_qhhSoynPmrvrPq4uMf8WNOdlmlUGrIjpSwYxkaOLmaPNAsnV_JYK4kmlImjtM3YyrFapTCh1OdxcL3TCzK7Gii92M__5L0RQjdjyxpoYi6WMIXWQVpQwIn092Y_ME99Y/s1600/phuket-2013-2-pd.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="640" data-original-width="960" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiivNstsQV_daY_qhhSoynPmrvrPq4uMf8WNOdlmlUGrIjpSwYxkaOLmaPNAsnV_JYK4kmlImjtM3YyrFapTCh1OdxcL3TCzK7Gii92M__5L0RQjdjyxpoYi6WMIXWQVpQwIn092Y_ME99Y/s320/phuket-2013-2-pd.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Foto dari Pak Denny</td></tr>
</tbody></table>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_cACWNAtF4i5J7axhjyaZQuuwWNuwMl4rE0b7j7_EmqjQ0ujL_R2oMex1hY3B-c6X-xmr7LKyXvTwzLy5Zr5yiZ4rdDcLbUV6zm1NMxIkzB_wTee_EsUlwJj9YJQ9J6V0kCzRoNr77_7L/s1600/phuket-pd.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="960" data-original-width="640" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_cACWNAtF4i5J7axhjyaZQuuwWNuwMl4rE0b7j7_EmqjQ0ujL_R2oMex1hY3B-c6X-xmr7LKyXvTwzLy5Zr5yiZ4rdDcLbUV6zm1NMxIkzB_wTee_EsUlwJj9YJQ9J6V0kCzRoNr77_7L/s320/phuket-pd.jpg" width="213" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">James Bond Island (foto dari Pak Denny)</td></tr>
</tbody></table>
Pembaca lama blog ini pasti sudah tahu kelanjutannya, yaitu +1 2.0 <a href="https://kupaskode.blogspot.com/2014/08/acm-icpc-2014-world-final-ekaterinburg.html">berhasil ke WF 2014</a> yang diadakan di Ekaterinburg, Rusia. Kami mengisi waktu semester genap dengan berlatih dengan keras. Ada juga sesi latihan "furious" pada liburan semester genap sebagai inisiatif dari Pak Denny (yang melahirkan "<a href="https://kupaskode.blogspot.com/2017/08/range-update-bit.html">magic BIT</a>").<br />
<br />
<hr />
<h3>
Tahun Keempat yang Mengejutkan</h3>
Setelah terbantai di WF 2014, kami pulang ke Indonesia. Kontes individual menentukan bahwa saya akan satu tim dengan Soko dan Ammar, kali ini untuk regional Jakarta dan Bangkok. Untuk tahun 2015, saya kurang yakin apakah bisa lulus ke WF lagi. Namun motivasi saya lebih ditujukan untuk membantu Soko dan Ammar untuk ke WF juga.<br />
<br />
Kami mengisi waktu dengan banyak berlatih bersama, dan juga melakukan "persilangan" team notebook. Berkat kreativitas Soko, tim kami bernama "BerinGAS", yang mana GAS merupakan gabungan nama kami bertiga.<br />
<br />
Hasil pertandingan di Bangkok suram, seingat saya bahkan tidak 10 besar. Rasanya mengecewakan, tapi apa dayanya kalau nasib kurang beruntung. Satu-satunya kesempatan hanya ada di regional Jakarta.<br />
<br />
Regional Jakarta 2014 akan menjadi ICPC regional terakhir saya. Tahun itu saya agak repot karena sebagai salah satu yang paling senior, saya menjadi <i><strike>warlord</strike></i> ketua kontingen untuk tim UI. Saya perlu menulis proposal ke fakultas untuk permohonan dana, dan mengurus administrasi peminjaman bis kuning. Untungnya saya ada pengalaman membantu Ashar membuat proposal ICPC tahun lalu. Selain itu juga saya menjadi ketua kontingen UI untuk Gemastik 2014, sehingga pengurusan bis bisa diatur bersamaan.<br />
<br />
Hasil kontes untuk regional Jakarta ini adalah yang paling memuaskan. Kami berada di peringkat ke-4. Hal mengesankan lainnya adalah Ammar menjadi <i>first solver</i> untuk 2 soal, sehingga didapatkan 2 balon bintang.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy2sjMIRunHzon6M4R1Ac5q7_8BDQciSfB-W-ZaPkjjfOXSwgv643CJBuONMztnoZ_YNIwOQKc-TnToQ1Swm6ShxNNPvvmYs4uy9BZ0oRDD3svq0KOOM-IUUYg0OkxfGm0E5LZrLOXTipJ/s1600/jakarta-2014-soko.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="720" data-original-width="960" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy2sjMIRunHzon6M4R1Ac5q7_8BDQciSfB-W-ZaPkjjfOXSwgv643CJBuONMztnoZ_YNIwOQKc-TnToQ1Swm6ShxNNPvvmYs4uy9BZ0oRDD3svq0KOOM-IUUYg0OkxfGm0E5LZrLOXTipJ/s320/jakarta-2014-soko.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="font-size: 12.8px;">Foto dari Soko</td></tr>
</tbody></table>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrFm8qTTW2wayElekUvchzEXBVUSLM0297_WbCbpjMP6DvNJOkKMxbxouR4DTDZH7YjCV_vm95eGFXnRBbnOWWysmwugyTE-UKjaq1S_mxeqYRH0SqzhdosfBhDXbSr5ofQBc0E9ZNp2n-/s1600/jakarta-2014-binus-best-national.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="640" data-original-width="960" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrFm8qTTW2wayElekUvchzEXBVUSLM0297_WbCbpjMP6DvNJOkKMxbxouR4DTDZH7YjCV_vm95eGFXnRBbnOWWysmwugyTE-UKjaq1S_mxeqYRH0SqzhdosfBhDXbSr5ofQBc0E9ZNp2n-/s320/jakarta-2014-binus-best-national.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Foto dari panitia ICPC</td></tr>
</tbody></table>
<br />
Meskipun demikian, hampir tidak mungkin kami lulus ke WF 2015 karena tidak berada di 3 besar...<br />
Pembaca lama blog ini pun akan tahu cerita selanjutnya, yaitu secara mengejutkan kami berhasil <a href="https://kupaskode.blogspot.com/2015/07/acm-icpc-world-final-2015-morocco.html">lulus ke WF 2015</a> yang akan diadakan di Marrakesh, Maroko.<br />
<br class="Apple-interchange-newline" />
<br />
<hr />
<h3>
Kesan Akhir</h3>
<div>
Seperti yang awalnya saya sampaikan, memutuskan untuk menginvestasikan sebagian dari 4 tahun kuliah ke ICPC merupakan komitmen yang panjang. Waktu tersebut mungkin dapat digunakan untuk mencari pengalaman di dunia kerja, bereksperimen dengan teknologi, mencari hobi, aktif di organisasi kampus, mencari uang dengan mengerjakan proyek, menjadi asisten riset, dan segudang aktivitas luar kuliah lainnya. Kalaupun sudah diputuskan untuk akan serius ICPC, mungkin ada kalanya akan "tergoda" dengan aktivitas lain yang hasil/kepuasannya dapat langsung dinikmati.</div>
<div>
<br /></div>
<div>
Mungkin tidak harus serius untuk mengejar WF. Saya juga melihat banyak rekan tim lain yang ikut ICPC sekedar untuk menikmatinya. Tentu saja ini tidak ada salahnya, dan kenyataannya mereka berkembang dari tahun ke tahun. Perkembangan dan pengalaman adalah proses yang paling penting!</div>
<div>
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinzIKeVtcOiNtY8AWddbf4ZR8NA2SPIOfsRhewjeyeteYhGhIHq6WHEO3i7zAlECEOSANC1J-v60kZK9R9NSMAy6H9UCRFihDWkRAu4YNxeNzFYa_dWreTYO8MgHxqNRkLtoaijZcTVZYp/s1600/jakarta-2013-3-pd.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="640" data-original-width="960" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinzIKeVtcOiNtY8AWddbf4ZR8NA2SPIOfsRhewjeyeteYhGhIHq6WHEO3i7zAlECEOSANC1J-v60kZK9R9NSMAy6H9UCRFihDWkRAu4YNxeNzFYa_dWreTYO8MgHxqNRkLtoaijZcTVZYp/s400/jakarta-2013-3-pd.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Kontingen UI pada ICPC Regional Jakarta 2013<br />
(foto dari Pak Denny)</td></tr>
</tbody></table>
<div>
<br /></div>
<br />William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-40739380026489773722019-11-29T16:21:00.002+07:002019-11-29T16:23:48.386+07:00Graf Berarah dengan Setiap Node Memiliki Tepat Satu KeluaranGraf merupakan konsep yang umum ditemui pada kompetisi pemrograman. Kali ini kita akan membahas graf dengan sifat yang menarik, yaitu setiap <i>node</i>-nya memiliki tepat satu keluaran (memiliki tepat 1 <i>outdegree</i>).<br />
<br />
Kalau digambar, grafnya terlihat seperti ini:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpRC7XI5HjTB-_lptJadocVkHTl43c6uqP4mXhfiwKC4aBbcaU88FF3GxGnHPRQQiyOg_HO5gH2jNVPULNf3Ws7VLJ6LhL6-5HUfMqEGP0KUCwdR3XBNI32829rp9X_TiAgvb5lJemxLSX/s1600/ee1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="366" data-original-width="550" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpRC7XI5HjTB-_lptJadocVkHTl43c6uqP4mXhfiwKC4aBbcaU88FF3GxGnHPRQQiyOg_HO5gH2jNVPULNf3Ws7VLJ6LhL6-5HUfMqEGP0KUCwdR3XBNI32829rp9X_TiAgvb5lJemxLSX/s320/ee1.png" width="320" /></a></div><br />
<br class="Apple-interchange-newline" /> <hr /><h3>Sifat 1: Dijamin terdapat <i>cycle</i></h3>Tidak mungkin kita dapat membentuk grafnya tanpa <i>cycle</i>. Jika terdapat N <i>node</i>, maka usaha terbaik yang dapat kita lakukan untuk menghindari <i>cycle</i> adalah membentuk struktur seperti rantai: setiap <i>node</i> menunjuk ke <i>node</i> lainnya. Sayangnya ujung terakhir dari struktur rantai ini harus menunjuk ke suatu <i>node</i> lainnya, yang mana pilihannya adalah <i>node</i>-<i>node</i> sebelumnya. Hal ini menjamin selalu terbentuk setidaknya sebuah <i>cycle</i>.<br />
<br />
<a name='more'></a><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuNX7FvFu_g6M9iiSOyNy4AdriI5Rk5qrxTQg8iCuRKjuYPhxDTHvSuIOZj5W-49RjFdGfGqng8UAuZZ_1a-REziUtWDbxe8MMNR9Bz3hYS6rI1aAsaFh3OQV3dksz85KJ3oHD4ZzX3vs6/s1600/ee3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="183" data-original-width="550" height="106" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuNX7FvFu_g6M9iiSOyNy4AdriI5Rk5qrxTQg8iCuRKjuYPhxDTHvSuIOZj5W-49RjFdGfGqng8UAuZZ_1a-REziUtWDbxe8MMNR9Bz3hYS6rI1aAsaFh3OQV3dksz85KJ3oHD4ZzX3vs6/s320/ee3.png" width="320" /></a></div><br />
<br class="Apple-interchange-newline" /> <hr /><h3>Sifat 2: Terdapat dua jenis <i>node</i>, yang merupakan anggota dari <i>cycle</i> dan yang menuju ke suatu <i>cycle</i></h3>Perhatikan gambar berikut. <i>node</i> berwarna merah merupakan anggota dari <i>cycle</i>, sementara yang berwarna putih adalah anggota dari <i>node</i> yang menuju ke suatu <i>cycle</i>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNNjoNZyCwGndmHgMtgxWm0RLpiCIvqNZ-JAz4jvmcZQIwj3wQUIdBe_buPvSdAjHj3EAjhTKL5s5cEe7EmxqlOt8-oloAYR5DGLsWoswr71j-IaNFkFyfaWMMW-ESRsDy48kn9HI6D70V/s1600/ee2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="366" data-original-width="550" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNNjoNZyCwGndmHgMtgxWm0RLpiCIvqNZ-JAz4jvmcZQIwj3wQUIdBe_buPvSdAjHj3EAjhTKL5s5cEe7EmxqlOt8-oloAYR5DGLsWoswr71j-IaNFkFyfaWMMW-ESRsDy48kn9HI6D70V/s320/ee2.png" width="320" /></a></div><br />
Apabila kita menelusuri grafnya dari sembarang <i>node</i>, dijamin kita akan sampai di suatu <i>cycle</i>.<br />
<br />
<hr /><h3>Sifat 3: Setiap <i>cycle</i> pasti terisolasi dengan <i>cycle</i> lainnya</h3>Supaya sebuah <i>node</i> dapat menjangkau lebih dari satu <i>cycle</i>, diharuskan ada <i>node</i> yang bercabang. Namun ini tidak mungkin terjadi karena setiap <i>node</i> memiliki tepat satu keluaran, artinya tidak ada <i>node</i> yang bercabang.<br />
<br />
Dengan demikian struktur graf pasti terdiri dari kelompok-kelompok <i>cycle</i> yang terisolasi; tidak mungkin suatu <i>node</i> menjadi anggota dari lebih dari 1 <i>cycle</i>.<br />
<br />
<hr /><h3>Sifat 4: Jika terdapat N <i>node</i>, maka dijamin terdapat maksimum O(N) <i>cycle</i></h3>Dari sifat sebelumnya, dipelajari bahwa tidak mungkin suatu <i>node</i> menjadi anggota dari lebih dari 1 <i>cycle</i>. Artinya <i>cycle </i>terbanyak yang dapat dihasilkan dibatasi oleh banyaknya <i>node. </i>Sebagai catatan, <i>cycle</i> paling banyak dapat dihasilkan dengan setiap <i>node</i> menunjuk ke dirinya sendiri.<br />
<br />
<hr /><h3>Memanfaatkan Sifat-Sifatnya</h3>Manfaatnya akan terasa saat menghadapi soal dengan graf berciri seperti ini. Misalnya dengan menyadari bahwa setiap <i>node</i> akan berakhir pada suatu <i>cycle</i>. Bisa juga dengan mendekomposisi graf menjadi beberapa kelompok <i>cycle</i>, lalu menyelesaikan setiap kelompok <i>cycle</i> secara independen.<br />
<br />
Sebagai contoh, perhatikan soal berikut:<br />
Pak Dengklek memiliki N bebek, dinomori dari 1 sampai dengan N. Setiap bebek memiliki tepat seekor bebek lainnya sebagai sahabat karibnya. Bebek mungkin saja menganggap dirinya sebagai sahabat, artinya bebek tersebut narsis.<br />
<br />
Pak Dengklek akan memberi cokelat kepada bebek ke-P. Karena bebek sangat setiakawan dengan sahabat karibnya, ia akan memberikan cokelat itu kepada sahabat karibnya. Tentu saja bebek yang menerima cokelat ini akan meneruskan cokelat tersebut ke sahabat karibnya lagi, membentuk rangkaian peristiwa beri-memberi yang mungkin tiada akhirnya.<br />
<br />
Pak Dengklek ingin tahu, pada peristiwa beri-memberi yang ke-K, bebek nomor berapa yang menerima cokelat tersebut?<br />
<br />
Batasan:<br />
1 ≤ N ≤ 100.000<br />
1 ≤ P ≤ N<br />
1 ≤ K ≤ 2.000.000.000<br />
<br />
Contoh Solusi<br />
Struktur sahabat karib ini membentuk graf yang baru dibahas. Solusi naif dengan mensimulasikan peristiwa beri-memberi sebanyak K kali memiliki kompleksitas O(K), terlalu lambat dan akan mendapat Time Limit Exceeded.<br />
<br />
Manfaatkan observasi bahwa suatu saat, cokelat akan “terperangkap” dalam suatu <i>cycle</i>.<br />
Ketika hal ini terjadi, kita dapat menggunakan sisa bagi banyaknya beri-memberi yang masih diperlukan terhadap ukuran <i>cycle</i>, untuk mengetahui bebek mana yang mendapatkan cokelat pada akhirnya.<br />
<br />
Dengan penelusuran graf DFS, <i>cycle</i> dijamin dicapai dalam O(N) langkah. Ukuran <i>cycle</i> dapat dicari dalam O(N) juga. Setelah itu mencari sisa bagi dapat dilakukan dalam O(1). Solusi ini bekerja dalam O(N). Perhatikan juga kasus khusus ketika peristiwa beri-memberi telah berlangsung K kali sebelum <i>cycle</i> ditemui.<br />
<br class="Apple-interchange-newline" /> <hr /><h3>Penutup</h3><div>Saya harap kini kalian menjadi lebih siap memulai analsis ketika menghadapi graf ini. Oh ya, sepertinya jenis graf ini merupakan yang paling saya sukai. </div><div><br />
</div><div>Untuk kasus yang lebih umum, yaitu ketika setiap <i>node </i>dapat memiliki sembarang keluaran, dekomposisi <i>cycle </i>perlu dilakukan dengan algoritma pencarian <i>Strongly Connected Component </i>(SCC). Selain itu, banyaknya <i>cycle </i>menjadi eksponensial terhadap N.</div><div><br />
</div>William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-7228478396932702232019-10-28T08:41:00.003+07:002020-04-23T03:00:45.244+07:00Petunjuk Coding: Binary Search<div style="display: none;"></div>Konsep binary search sederhana; pada data terurut, periksa nilai di tengah, lalu putuskan apakah pencarian dilanjutkan di setengah awal atau akhir. Kenyataannya, implementasinya tidak semudah itu. Tulisan ini akan membahas implementasi berbagai macam binary search.<br />
<br />
<hr /><h3>Jenis 1: data diskret</h3>Biasanya kita diberikan sebuah array terurut berisi N nilai, lalu diminta mencari index untuk suatu nilai v.<br />
<br />
Implementasi yang saya sukai adalah secara iteratif, menggunakan while. Representasikan rentang pencarian dengan dua variabel, sebut saja l dan r. Selama rentang pencarian tidak kosong (yaitu l <= r), periksa nilai di tengah. Kalau nilai di tengah sama dengan v, hentikan pencarian. Kalau lebih kecil, geser batas bawah rentang pencarian menjadi 1 indeks setelah bagian tengahnya. Kalau lebih besar, geser batas atas rentang pencarian menjadi 1 indeks sebelum bagian tengah.<br />
<br />
Berikut implementasi lengkapnya. Kalau sampai keluar dari while dan variable ans masih bernilai -1, artinya nilai v tidak ditemukan.<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
int l = 0;
int r = N-1;
int ans = -1;
while (l <= r) {
int m = (l + r)/2;
if (ar[m] == v) {
ans = m;
break;
} else if (ar[m] < v) {
l = m + 1;
} else {
r = m - 1;
}
}
]]></script><!-----></div><br />
Variasi lain yang memusingkan adalah kalau v tidak ditemukan, cari nilai terdekat yang lebih besar.<br />
Solusinya sama seperti sebelumnya, kecuali ketika nilai di tengah <u>mungkin</u> menjadi jawaban, catat indeksnya. Setelah rentang pencarian habis, dijamin indeks terakhir yang dicatat menyimpan jawaban yang diinginkan. Berikut implementasinya:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
int l = 0;
int r = N-1;
int ans = -1;
while (l <= r) {
int m = (l + r)/2;
if (ar[m] < v) {
l = m + 1;
} else { // ar[m] >= v
ans = m; // Catat kandidat jawaban
r = m - 1;
}
}
]]></script><!-----></div><br />
Apabila nilai v tidak ditemukan dan yang diinginkan adalah nilai terdekat yang lebih kecil, cukup ubah kondisinya:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
int l = 0;
int r = N-1;
int ans = -1;
while (l <= r) {
int m = (l + r)/2;
if (ar[m] <= v) {
ans = m; // Catat kandidat jawaban
l = m + 1;
} else { // ar[m] > v
r = m - 1;
}
}
]]></script><!-----></div><br />
Kedua variasi ini adalah binary search yang akan sering Anda temukan pada kompetisi. Pastikan Anda hafal kapan jawaban perlu dicatat.<br />
<br />
<hr /><h3>Jenis 2: Data Kontinu</h3>Biasanya kita tidak diberikan array berisi data, melainkan kita diminta menebak suatu bilangan riil yang memenuhi suatu kondisi. Anggap saja kita memiliki fungsi f, yang menerima bilangan riil antara 0 sampai 1 dan mengembalikan true jika dan hanya jika kondisi terpenuhi. Lalu anggap fungsi f ini memiliki sifat khusus, yaitu terdapat suatu bilangan v, sehingga setiap x yang memenuhi x≥v pasti memiliki nilai f(x) bernilai true.<br />
<br />
Sama seperti sebelumnya, buat dua variabel yang merepresentasikan rentang pencarian.<br />
Lalu terdapat setidaknya dua pandangan tentang implementasinya, yaitu menggunakan while sampai rentang pencarian hampir tidak berubah lagi seperti berikut:<br />
</v><br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
double l = 0;
double r = 1;
while (fabs(l - r) > 1e-8) {
double m = (l + r)/2;
if (!f(m)) {
l = m;
} else {
r = m;
}
}
// Jawaban akhir ada di l
]]></script><!-----></div><br />
Pada implementasi di atas, dipilih nilai 10<sup>-8</sup> sebagai nilai toleransi. Kalau beda rentang pencarian sudah lebih kecil dari nilai tersebut, pencarian dianggap selesai dan l dianggap sebagai jawabannya.<br />
<br />
Pandangan implementasi lainnya adalah dengan menggunakan for sebanyak K kali. Nilai K biasanya 100, yang mana sudah sangat akurat:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
double l = 0;
double r = 1;
const int K = 100;
for (int i = 0; i < K; i++) {
double m = (l + r)/2;
if (!f(m)) {
l = m;
} else {
r = m;
}
}
// Jawaban akhir ada di l
]]></script><!-----></div><br />
Secara pribadi saya lebih menyukai strategi yang menggunakan for. Alasannya adalah dijamin setelah 100 langkah, program akan berhenti; sementara untuk strategi dengan while, tidak ada jaminan kapan program berhenti. Saya selalu menggunakan strategi dengan for dan tidak pernah ada masalah.<br />
<br />
<hr /><h3>Penutup</h3>Sekian petunjuk untuk mengimplementasikan binary search. Semoga bermanfaat!William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-43400340893436195822019-09-27T14:14:00.002+07:002019-09-27T14:15:13.061+07:00Penulisan Buku Pemrograman Kompetitif Dasar<div style="display: none;">
</div>
Libur akhir tahun baru saja lewat. Saya jadi ingat masa-masa liburan akhir tahun 2017 yang dihabiskan untuk menulis buku Pemrograman Kompetitif Dasar (PKD). Proses penulisan buku itu cukup lama. Banyak yang sebelumnya tidak diketahui jadi harus diketahui. Saya akan membagikan pengalaman sepanjang penulisan buku PKD.<br />
<br />
<h3>
Awal Mula</h3>
Pada tahun 2014, saya diminta Ashar untuk menuliskan materi pemrograman dasar Pascal untuk dimuat ke TLX Training Gate (seterusnya akan saya sebut TLX-TG). Lalu tahun 2016, ada inisiatif untuk memperluas cakupan materi TLX-TG, sehingga dibuatlah materi pemrograman kompetitif dasar yang isinya lebih ke algoritma-algoritma. Seluruh materi ini ditulis dengan format slide presentasi (seperti materi perkuliahan). Argumennya adalah slide presentasi lebih ringkas daripada penjelasan berupa paragraf, dan dapat memuat gambar yang dianimasikan. Animasi yang saya maksud sebenarnya sesederhana seperti slide 1 memuat array, lalu slide kedua memuat array yang elemennya ditukar; misalnya digunakan untuk mengajarkan pengurutan.<br />
<br />
Setelah satu tahun berlalu, Aji tiba-tiba mengajak saya untuk menganalisis kemajuan pemrograman kompetitif di Indonesia. Saya pun terpikir, apakah selama ini materi yang ditulis benar-benar digunakan dan bermanfaat bagi pelajar Indonesia?<br />
<br />
Sepanjang saya menulis materi pemrograman kompetitif dasar, sebenarnya ada keraguan apakah slide media yang tepat. Slide mungkin unggul kalau digunakan oleh pengajar, seperti dosen atau guru. Namun kali ini konteksnya adalah pembelajaran mandiri. Saya pribadi lebih suka belajar hal baru dengan membaca tulisan yang penjelasannya lengkap, seperti buku atau artikel.<br />
<br />
Analisis tahun 2017 itu dimulai dengan memberikan survey kepada anggota grup facebook "Olimpiade Informatika Indonesia". Saya menggunakan kesempatan ini untuk mengetahui apa pendapat peserta didik tentang media pembelajaran TLX-TG. Salah satu pertanyaan survey tersebut adalah mengurutkan preferensi terhadap empat media pembelajaran:<br />
<ol>
<li>Slide presentasi</li>
<li>Tulisan artikel/blog</li>
<li>Buku</li>
<li>Video pengajaran</li>
</ol>
<div>
Ternyata keraguan saya terbukti benar. Yang lebih mengejutkan adalah slide presentasi berada di peringkat paling bawah. Media pembelajaran yang lebih diminati adalah video pengajaran, disusul dengan buku.</div>
<div>
<br /></div>
<div>
Memproduksi video pengajaran jelaslah sulit. Diperlukan peralatan untuk merekam video, microphone yang bagus untuk kualitas suara yang bagus, kemampuan mengedit, dan "sosok berkharisma" yang melakukan pengajaran. Lagipula, video pengajaran yang diakses lewat internet tidak menjamin kemudahan akses. Kualitas internet di Indonesia sangat beragam, dan saya sendiri kadang kesulitan untuk nonton video youtube. Singat cerita, video pengajaran tidak dapat dikejar.</div>
<div>
<br /></div>
<div>
Pilihan selanjutnya adalah buku. Mungkin buku adalah media pembelajaran yang paling seimbang dari keempat yang dicalonkan, karena:</div>
<div>
<ul>
<li>Penjelasan dapat diberikan secara lengkap, tanpa perlu khawatir halaman menjadi penuh tulisan. Ini perlu dihindari pada penulisan slide.</li>
<li>Pembelajarannya sistematis, tidak lompat-lompat atau tercecer seperti tulisan artikel/blog pada umumnya.</li>
<li>Lebih mudah diakses daripada video pengajaran.</li>
<li>Dapat dicetak, lalu dikirimkan ke segala pelosok di Indonesia.</li>
</ul>
<div>
Yah, memang buku memiliki kekurangannya tersendiri. Anak SMP/SMA yang diminta membaca buku pemrograman atau algoritma penuh tulisan mungkin akan terintimidasi. Buku pelajaran sekolah biasanya memiliki gambar dan berwarna. Bahkan, kadang gambarnya tidak penting dan hanya berperan sebagai dekorasi, misalnya pelajaran ekonomi tentang barter lalu diberikan gambar orang menukar beras dengan kambing.</div>
</div>
<div>
<br /></div>
<div>
Selama diskusi dengan teman-teman TOKI berlangsung, keputusannya lebih condong ke penulisan buku. Tujuannya adalah membuat buku yang lengkap, sistematis, dan gratis untuk mempermudah pembelajaran mandiri. Namun saya ragu, dan mempertanyakan apa gunanya buku ini kalau sudah ada buku Competitive Programming 1 (CP1), yang sudah digratiskan oleh Steven & Felix Halim? Argumen yang diberikan adalah CP1 menggunakan Bahasa Inggris, dan tidak semua siswa-siswi sudah lancar berbahasa Inggris. Setelah diyakinkan masih ada <i>niche</i>, akhirnya diputuskanlah untuk menulis buku.</div>
<div>
<br /></div>
<div>
Rencananya adalah membukukan slide materi, ditambah dengan beberapa materi baru. Saya dan Aji berkomitmen menjadi penulis utama buku ini, dan berencana menyelesaikannya sebelum PJJ pra-OSN 2018.</div>
<div>
<br />
<h3>
Mulai Menulis Buku</h3>
</div>
<div>
Penulisan dimulai dengan mencari template buku. Idealnya penulisan akan dilakukan dengan Latex, karena gratis dan mudah diintegrasikan dengan version control seperti Git. Aji menemukan template buku yang sekiranya cocok, yaitu tidak terlalu ilmiah dan tidak terlalu santai. Diputuskan untuk sementara menggunakan template buku tersebut.</div>
<div>
<br /></div>
<div>
Selanjutnya dilakukan transformasi slide presentasi ke bentuk buku, atau lebih tepatnya "transpile" kode latex beamer slide ke format latex buku. Aji menulis script untuk melakukannya, dan jadilah buku sangat mentah versi 0.<br />
<br />
Tugas selanjutnya adalah mengubah format poin-poin dalam slide menjadi narasi. Aji mengusulkan untuk merekrut sukarelawan untuk membantu proses ini. Jadinya dilaksanakan "hackathon" di kantor Sirclo atas bantuan Brian dan Ashar. Secara mengejutkan cukup banyak yang berminat untuk membantu. Kini bukunya lebih berwujud buku daripada sebelumnya, walaupun gaya penulisan setiap bab gado-gado karena dikerjakan oleh orang yang berbeda. Orang-orang yang membantu hackathon ini namanya dicantumkan sebagai kontributor buku.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiKwhpevHd_G7mh2FfFKPsgmav8u-lpcsdSEWulvW-WfKL4gwR67JWaN4BNlNJ5xgHq2bSVSveQrfzeoyiI3k9vrv82dIZLH0mpuVbIwLWAVyTmDRN60P-hbr115xMwMH-skDeUhs2A59i/s1600/hackathon-pkd.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="494" data-original-width="659" height="239" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiKwhpevHd_G7mh2FfFKPsgmav8u-lpcsdSEWulvW-WfKL4gwR67JWaN4BNlNJ5xgHq2bSVSveQrfzeoyiI3k9vrv82dIZLH0mpuVbIwLWAVyTmDRN60P-hbr115xMwMH-skDeUhs2A59i/s320/hackathon-pkd.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Peserta Hackathon PKD</td></tr>
</tbody></table>
Memasuki libur hari natal dan tahun baru, kantor saya libur selama 2 minggu. Saya menghabiskan waktu bersama Aji untuk merapikan dan menyeragamkan penulisan setiap bab. Setiap bab diperiksa oleh kami, dari awal sampai akhir. Setelah itu bukunya sudah bisa disebut dengan "versi 0.0". Sebetulnya sempat ada perdebatan apakah bukunya mau diberi versi dalam bentuk <major>.<minor>.<patch> yang klasik, atau <major>.<patch> saja. Diputuskan untuk menggunakan <major>.<patch> saja karena tidak jelas kapan minor atau patch bertambah.<br />
<br />
<h3>
Pemolesan Konten</h3>
Tahap selanjutnya adalah review. Ashar dan Aji mengusulkan untuk merekrut Pak Suhendry. Untungnya Pak Suhendry bersedia membantu.</div>
<div>
<br />
Awalnya saya percaya diri kalau bukunya ditulis dengan baik. Ternyata pemikiran itu terbukti salah ketika ditemukan kekeliruan di sana sini. Saya belajar kalau pihak ketiga dibutuhkan untuk memberikan masukan, sehingga hal-hal yang tidak kita pikirkan bisa diketahui.<br />
<br />
Pak Suhendry memberikan review yang SANGAT BERMANFAAT. Banyak kekeliruan yang ditemukan, dan hal-hal kecil yang terlewatkan. Saya ingat saran yang paling bermanfaat adalah memperbaiki sistematika penulisan. Misalnya pembahasan perlu dimulai dari A dulu, lalu dilanjutkan ke B, dan seterusnya.<br />
<br />
<h3>
Dekorasi dan Estetika</h3>
<div>
Setelah kontennya memenuhi standar yang kita tetapkan, saya dan Aji mulai menggarap buku ini dari segi tampilan. Kami merekrut Ali untuk memberi masukan tentang font, skema warna, gambar cover, dan sebagainya. Setelah konsepnya jelas, saya menulis ulang template Latex bukunya sehingga memenuhi konsep yang kita tetapkan.</div>
<div>
<br /></div>
<div>
Bagian yang menyusahkan di sini adalah menulis template Latex itu tidak semudah menulis HTML + CSS (walaupun CSS sendiri pun tidak mudah). Banyak kode-kode misterius, konfigurasi library yang saling bertentangan, dan sebagainya. Setelah berdarah-darah, akhirnya selesai juga.</div>
<div>
<br /></div>
<div>
Suatu buku kurang sempurna apabila tidak ada kata pengantar dan basa-basinya. Jadi kami juga menambahkan kata pengantar, berikut yang lebih pentingnya, yaitu ucapan terima kasih kepada kontributor.<br />
<br /></div>
<div>
<h3>
Publikasi</h3>
</div>
<div>
Kini buku sudah sangat berbentuk buku, tinggal dipublikasikan saja. Kami coba bertanya kepada alumni TOKI yang pernah menulis buku, dan ternyata kalau bukunya mau dijual di toko, tidak mungkin bisa non-profit. Toko buku pasti mau keuntungan.<br />
<br />
Kalau pihak kita yang mengelola percetakannya, rasanya repot. Kita seakan-akan menjadi penjual toko online. Jadinya kami mulai dengan mempublikasikan dalam bentuk e-book.<br />
<br />
<h3>
Pelajaran</h3>
</div>
</div>
<div>
<ol>
<li>Menulis buku itu melelahkan! Butuh kesabaran, ketangguhan, dan niat untuk menghabiskan berbulan-bulan untuk menyelesaikannya. Untungnya ada teman-teman TOKI dan Aji yang memotivasi.</li>
<li>Harus mampu menulis Bahasa Indonesia dengan benar. Walaupun kita menggunakan bahasa itu setiap hari, penulisannya tidak semudah berbicara. Pertanyaan yang sering muncul kira-kira seperti "apakah ada spasi sebelum 'pun'?".</li>
<li>Walaupun awalnya berdarah-darah, penggunaan Latex sangat membantu. Kita dapat menulis buku dengan editor apapun, lalu dimuat di version control dengan mudah. Kalau disuruh menulis buku lagi, saya pasti pakai Latex. </li>
</ol>
<div>
<br /></div>
</div>
Demikianlah pengalaman penulisan buku ini. Apa yang akan terjadi dengan buku ini pada masa depan? Apakah ada edisi kedua? Atau apakah akan ada PKL (<strike>Penjual Kaki Lima</strike> Pemrograman Kompetitif Lanjut)? Mari anggap saja itu pertanyaan untuk hari esok.<br />
<br />
Seperti biasa, saya harap buku ini menjadi pedoman belajar pemrograman bagi kalian yang baru memulai. Semoga bermanfaat.William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-2195424374749452442019-09-27T12:50:00.002+07:002019-09-27T12:50:45.707+07:00Video Seri Persiapan OSN Informatika<br />
Sekitar 2 tahun yang lalu, saya dan Aji melakukan survey di grup Facebook Olimpiade Informatika Indonesia. Tujuan surveynya adalah mengukur apakah Training Gate TLX efektif dalam memberi pelajaran, dan juga media apa yang diharapkan ada untuk membantu pembelajaran. Hasilnya, media yang paling diminati adalah video pembelajaran, diikuti dengan buku, dan lain-lain. Berhubung tenaga kerja kami terbatas, maka dibuatlah buku terlebih dahulu.<br />
<br />
Entah dari mana datangnya, tiba-tiba tim konten TOKI mendapatkan "hibah" tenaga kerja. Ali yang baru selesai studi mau membantu merekam dan mengedit video, sementara Aji dan Felix Jingga menyiapkan materinya. Pada akhirnya, mereka ditambah Andreas sukses merekam video pembelajaran yang dipublikasikan lewat youtube.<br />
<br />
Pembelajaran melalui video tentunya sangat membantu, terutama mereka yang lebih cepat belajar dengan cara mendengarkan daripada membaca (cara belajar visual vs auditori). Saya menyarankan kalian yang ingin belajar OSN informatika untuk mencoba menonton videonya.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><iframe width="320" height="266" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/dnpkD_p12WM/0.jpg" src="https://www.youtube.com/embed/dnpkD_p12WM?feature=player_embedded" frameborder="0" allowfullscreen></iframe></div><br />
Hingga tulisan ini dibuat, sudah ada 16 video. Daftar videonya bisa anda cek di sini: <a href="https://www.youtube.com/watch?v=dnpkD_p12WM&list=PL42SmLrOBFuRoDXatrv1PajCbt2ejb2Gn">https://www.youtube.com/watch?v=dnpkD_p12WM&list=PL42SmLrOBFuRoDXatrv1PajCbt2ejb2Gn</a>.<br />
<br />
Kini bertambahlah sumber pembelajaran kalian. Mulai dari forum, website, buku, sampai video. Yang diperlukan hanyalah motivasi dan semangat dari kalian. Jadi, selamat belajar!<br />
William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com1tag:blogger.com,1999:blog-2894426456296176040.post-70250795592398107222019-04-02T13:53:00.000+07:002019-04-09T13:06:33.715+07:00Terasi (bagian 4): Migrasi ke C++<div style="display: none;">
</div>
Di tengah-tengah pengerjaan front end, tiba-tiba saya ada mendapatkan komitmen pekerjaan baru yang membuat proyek Terasi perlu ditunda dulu. Jadi sekitar bulan Juni 2016, saya berhenti untuk sementara.<br />
<br />
<hr />
<h3>
Lanjutan Pengerjaan</h3>
<div>
Kini sudah bulan Juni 2017. Sekitar setahun setelah Terasi ditinggal.<br />
<br />
Saya sudah beres urusan pekerjaan lainnya dan dapat kembali melanjutkan proyek Terasi. Sebelum langsung melanjutkan, saya periksa dulu apakah tiba-tiba sudah muncul aplikasi yang serupa.</div>
<div>
<br /></div>
<div>
Dari teman, saya tahu kalau Google Maps sudah mengintegrasikan jalur Transjakarta. Jadi saya lihat-lihat. Ternyata implementasinya masih sangat kasar. Jadi ketibaan bus di setiap halte di-hardcode "setiap 15 menit". Ditulisnya data didapatkan dari Transportasi Jakarta. Mungkin karena mereka belum memiliki data yang akurat, jadinya digunakan estimasi kasar 15 menit.</div>
<div>
<br /></div>
<div>
Dari teman lagi, ada sebuah aplikasi yang bernama "Trafi". Saya periksa, dan aplikasinya bagus. Saat itu kita bisa melihat posisi bus secara live. Sepertinya API yang digunakan sama dengan yang saya pakai. Melihat estimasi waktu tunggu, lagi-lagi waktunya di-hardcode per halte.<br />
<br /></div>
<div>
Kesimpulannya, saya bisa lanjut mengerjakan Terasi.</div>
<div>
<br /></div>
<div>
Oke lalu saya lihat data yang sudah dipanen. Secara mengejutkan ukuran data 1 hari menjadi besar. Setelah dibandingkan dengan data tahun lalu, ini yang saya lihat:</div>
<div>
<br /></div>
<div>
<pre><span style="font-size: x-small;">gyosh@gyosh ~/workspace/terasi/data $ ls -l --block-size=M | grep 01-01.tar.gz
-rw-r--r-- 1 gyosh gyosh 12M Jan 1 2016 2016-01-01.tar.gz
-rw-r--r-- 1 gyosh gyosh 25M Mar 18 2017 2017-01-01.tar.gz</span></pre>
</div>
<div>
<br /></div>
<div>
Perhatikan kolom ke-5 (1 based). Terlihat bahwa dulunya 12 MB, kini 25 MB. Jadi ukurannya menjadi 2 kali lebih besar!<br />
Pastinya ini hal yang baik untuk Jakarta, karena banyaknya bus bertambah. Lalu tiba-tiba terpikir, atau jangan-jangan ada koridor baru?<br />
<br />
Jadi saya periksa peta Transjakarta terbaru, dan ternyata benar. Muncul lebih banyak subkoridor yang namanya berupa angka + huruf, seperti 5A, 5B, 5C, 5D, 5E. Koridor seperti itu biasanya menggunakan sebagian besar koridor 5, tetapi halte awal dan akhirnya mungkin saja berbeda. Selain itu, ada juga koridor baru dengan kode huruf + angka, seperti S11, S21, T11, dan sebagainya. Koridor itu sepertinya mencapai luar Jakarta, seperti daerah "-bodetabek".<br />
<br />
Selanjutnya, saya coba menonton rekam jejak terbaru di aplikasi visualisasi saya. Ternyata data bus baru itu tidak terekam. Bus-busnya masih menggunakan informasi koridor yang lama. Artinya tidak ada bus yang mengaku di koridor 5A, 5B, s/d 5E. Semuanya mengaku di koridor 5. Lalu muncul juga banyak bus dengan kode koridor "NBRT".<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXHOJ94wVPrz5LF7cY6FNuIlWD6lB4SPufdOVdhirUQRN3prXm_bf1xNO76JEoncItl79IgUT17PNmSXT9U8Swz-YWMv5RQ3vRwVioKh-IlypbqWsp7CVVhJfDELQFI16mVIcZXjZLytVO/s1600/nbrt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="655" data-original-width="666" height="392" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXHOJ94wVPrz5LF7cY6FNuIlWD6lB4SPufdOVdhirUQRN3prXm_bf1xNO76JEoncItl79IgUT17PNmSXT9U8Swz-YWMv5RQ3vRwVioKh-IlypbqWsp7CVVhJfDELQFI16mVIcZXjZLytVO/s400/nbrt.png" width="400" /></a></div>
<br />
Saya menduga kalau NBRT ini adalah bus pengumpan (Feeder Bus) Transjakarta. Merekalah yang saya sebut subkoridor, seperti 1A, 1B, 5A, 5B, dsb.<br />
<br />
<a name='more'></a><br /><br />
Semua perubahan ini tentunya bagus untuk Jakarta. Namun tidak bagus bagi saya karena lagi-lagi saya harus mengumpulkan polyline setiap rutenya. Selain itu, program untuk ekstraksi dan agregasi yang saya buat berjalan cukup lama. Untuk memproses data 1 hari tahun 2016, dibutuhkan waktu 5 menit. Kalau sekarang datanya berlipat ganda, berarti 10 menit.<br />
<br />
Meningkatnya waktu eksekusi cukup merepotkan. Mungkin pertumbuhan 2x tidak seberapa besar. Namun, kalau dalam 1 tahun saja mereka dapat meningkatkan layanannya sebesar 2x, saya tidak tahu apakah tahun depan datanya akan lebih banyak.<br />
<br />
Ternyata memang benar. Data terakhir yang dipanen tanggal 23 Agustus 2017 memiliki ukuran 55 MB. Dalam waktu 8 bulan, mereka menggandakan layanan busnya...<br />
<br />
<hr />
<h3>
Penyusunan Ulang Komponen</h3>
Karena saya akan sering menjalankan proses ekstraksi dan agregasi untuk keperluan tuning, rasanya tidak menyenangkan kalau sekali mengolah data 1 hari membutuhkan waktu puluhan menit.<br />
<br />
Melihat apa yang sudah saya kerjakan, sebenarnya ada banyak kekurangan:<br />
<ol>
<li>Ekstraksi dan agregasi merupakan kegiatan yang banyak hitung-hitungan. Rasanya menulisnya dengan Javascript tidaklah bijak. Untuk skala data yang besar, lebih baik menggunakan bahasa pemrograman dengan tipe data statis seperti Java atau C++.</li>
<li>Ekstraksi dan agregasi hanya bisa berjalan di 1 core CPU. Komputer saya memiliki 4 core, jadi sebaiknya dimanfaatkan juga untuk memparalelisasi hitung-hitungannya.</li>
</ol>
<br />
Jadi saya berpikir, apakah lebih baik saya menuliskan kembali programnya dalam C++? Lalu saya teringat bahwa kita dapat "mengawinkan" Node.js dengan C++ seperti yang dijelaskan di sini: <a href="http://benfarrell.com/2013/01/03/c-and-node-js-an-unholy-combination-but-oh-so-right/">http://benfarrell.com/2013/01/03/c-and-node-js-an-unholy-combination-but-oh-so-right/</a><br />
<br />
Konsepnya adalah kita menulis request routing dalam Node.js, tetapi modul utamanya menggunakan C++. Jadi saya bisa menulis program C++ yang membaca data, menyimpannya dalam array, dan mengembalikan data sesuai permintaan API dari Node.js. Dengan array bertipe data statis, saya bisa menggunakan in memory database dan mengkompres ukuran memorinya (misalnya menggunakan tipe data shortint daripada int 32 bit).<br />
<br />
Karena keuntungan C++ cukup besar dalam hal pengolahan dan penyajian data, akhirnya saya memutuskan untuk menulis ulang programnya. Tidak ada perubahan besar dalam struktur aliran data, hanya saja bagian database kini diganti menjadi file biasa.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAPBOkrf59IDS3_SWolrA4Kck93N-5wPrP91jz5c17ifppylG3PmIkMegNnpc8x-3zpqeRo7kdPGISo5lqIIe0ib01vEpVZmD3QxizCtXCS29kNfrfi-cg_CIZVmjJg4-1ECWhlBKBtQPR/s1600/g7556.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="314" data-original-width="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAPBOkrf59IDS3_SWolrA4Kck93N-5wPrP91jz5c17ifppylG3PmIkMegNnpc8x-3zpqeRo7kdPGISo5lqIIe0ib01vEpVZmD3QxizCtXCS29kNfrfi-cg_CIZVmjJg4-1ECWhlBKBtQPR/s1600/g7556.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br /></div>
<div>
<br class="Apple-interchange-newline" />
<hr />
<h3>
Coding Trance</h3>
</div>
<div>
Selama beberapa minggu selanjutnya, saya coding terus-terusan. Hasil terakhir yang dicapai adalah seluruh modul selesai ditulis ulang ke C++ dan performanya menjadi jauh lebih cepat. Pengolahan data untuk satu hari kini berlangsung dalam 30 detik.<br />
<br />
Saya juga menambahkan fitur baru, yaitu mencari informasi perjalanan dari suatu halte ke halte lainnya. Algoritma yang digunakan adalah A* Search, dengan heuristik berupa aproksimasi Great Circle Distance antara posisi saat ini dengan posisi tujuan (sebenarnya saya hanya menghitung jarak Euclidean antara longitude/latitude posisi awal dan akhir). Jadi saya precompute dulu untuk seluruh kemungkinan pasangan halte, jalankan A*, lalu simpan hasilnya dalam bentuk berkas .txt. Seperti biasa, berkas ini akan disimpan dalam in memory database.<br />
<br />
Selain memberikan informasi halte mana saja yang dikunjungi, durasi menunggu atau perjalanan juga diberikan. Untuk saat ini saya hanya menggunakan waktu median menunggu dan perjalanan, lalu menjumlahkannya.<br />
<br />
Berikut salah satu hasil untuk perjalanan dari RS. Harapan Kita menuju ke Blok M dengan asumsi sampai di halte RS. Harapan Kita pukul 06:00:<br />
<pre><span style="font-size: x-small;">{
"duration": 25,
"transit": 1,
"summary": "9/9A - 1",
"path": [
{
"busStop": "RS Harapan Kita",
"action": "wait for bus",
"corridors": [
[
"9",
"Pinang Ranti"
],
[
"9A",
"PGC 2"
]
],
"time": "06:00",
"duration": 3
},
{
"busStop": "Slipi Kemanggisan",
"action": "inside bus",
"corridors": [],
"time": "06:03",
"duration": 1
},
{
"busStop": "Slipi Petamburan",
"action": "inside bus",
"corridors": [],
"time": "06:04",
"duration": 3
},
{
"busStop": "Senayan JCC",
"action": "inside bus",
"corridors": [],
"time": "06:07",
"duration": 1
},
{
"busStop": "Semanggi",
"action": "inside bus",
"corridors": [],
"time": "06:08",
"duration": 1
},
{
"busStop": "Semanggi",
"action": "get out of bus",
"corridors": [],
"time": "06:09",
"duration": 0
},
{
"busStop": "Bendungan Hilir",
"action": "walk in inter station bridge",
"corridors": [],
"time": "06:09",
"duration": 6
},
{
"busStop": "Bendungan Hilir",
"action": "wait for bus",
"corridors": [
[
"1",
"Blok M"
]
],
"time": "06:15",
"duration": 3
},
{
"busStop": "Polda Metro Jaya",
"action": "inside bus",
"corridors": [],
"time": "06:18",
"duration": 1
},
{
"busStop": "Gelora Bung Karno",
"action": "inside bus",
"corridors": [],
"time": "06:19",
"duration": 1
},
{
"busStop": "Bundaran Senayan",
"action": "inside bus",
"corridors": [],
"time": "06:20",
"duration": 1
},
{
"busStop": "Masjid Agung",
"action": "inside bus",
"corridors": [],
"time": "06:21",
"duration": 1
},
{
"busStop": "Blok M",
"action": "inside bus",
"corridors": [],
"time": "06:22",
"duration": 3
},
{
"busStop": "Blok M",
"action": "get out of bus",
"corridors": [],
"time": "06:25",
"duration": 0
}
]
}</span></pre>
Berikut posisinya di peta:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi222wZSXAMHcPVEd6p4DMcDzg8WSn4-E4urtFdZ6dwFbSFHp1U40L1yNITOaYvQRbDNp07cZwuAwr1jmfFGKa_53YW-Q9Um5NUcQlCE3PBVO2Ys7Jb7pZe6tZ6vxKB-a0QjTItxgYOKlc5/s1600/travel.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="487" data-original-width="278" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi222wZSXAMHcPVEd6p4DMcDzg8WSn4-E4urtFdZ6dwFbSFHp1U40L1yNITOaYvQRbDNp07cZwuAwr1jmfFGKa_53YW-Q9Um5NUcQlCE3PBVO2Ys7Jb7pZe6tZ6vxKB-a0QjTItxgYOKlc5/s1600/travel.jpg" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Peta dari <a href="http://transjakarta.co.id/">http://transjakarta.co.id/</a></td></tr>
</tbody></table>
<br />
Nilai "action" menyatakan apa yang perlu dilakukan. Nilai aslinya adalah angka, seperti 0 (tunggu bus), 1 (di dalam bus), 2 (turun bus), dsb. Namun untuk keperluan penjelasan, saya menggantinya menjadi teks saja.<br />
<br />
Terdapat beberapa alternatif jalur yang diberikan. Kadang-kadang tidak masuk akal, seperti perlu memutar terlebih dahulu. Kalaupun tidak memutar, jalur bus yang diberikan adalah jalur yang jarang ada busnya (misalnya subkoridor yang sudah tidak aktif). Saya perlu sedikit menambahkan penyaringan hasil seperti membuang jalur yang waktu tempuhnya 1,5x lebih lama dari yang terbaik, atau yang transitnya lebih dari 3x.<br />
<br />
<hr />
<h3>
Akhir Proyek</h3>
</div>
<div>
Suatu hari, saya iseng melihat kembali aplikasi Trafi.</div>
<div>
<br /></div>
<div>
Secara mengagetkan, mereka memperbaharui aplikasi mereka. Kini data mereka lengkap mencakup seluruh koridor baru, dapat menunjukkan informasi secara live, dan terlihat cantik. </div>
<div>
<br /></div>
<div>
Reaksi saya ya kaget. Entah bagaimana mereka bisa mendapatkan datanya. Langsung deh saya mengaku kalah dan tidak lagi melanjutkan proyek ini :")</div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgE9fO9b06HniqcStJDhPUPEk38mowbCcAWWrtEQYVf-dJ0ONIGSNmgyrdRW9fuwYxayZqSVZaS7tuvp5T0mdYRLt-a00Ob87ZdMuqLIIkzXCuboJa2kfDm3AWS6uUQhhMTl5h7gvoFvBac/s1600/yao_ming.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="232" data-original-width="200" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgE9fO9b06HniqcStJDhPUPEk38mowbCcAWWrtEQYVf-dJ0ONIGSNmgyrdRW9fuwYxayZqSVZaS7tuvp5T0mdYRLt-a00Ob87ZdMuqLIIkzXCuboJa2kfDm3AWS6uUQhhMTl5h7gvoFvBac/s200/yao_ming.jpg" width="171" /></a></div>
<div style="text-align: center;">
<br /></div>
Memang akhir cerita ini anti klimaks. Tapi saya rasa tidak ada yang sia-sia. Saya belajar banyak hal dari pengerjaan proyek ini, seperti:</div>
<div>
<ol>
<li>Membuat sistem logging yang tepat, terutama pada pemanen data dan melaporkan adanya error.</li>
<li>Pembuatan "internal tool" seperti visualisasi data sangat penting. Meskipun pada akhirnya tidak digunakan oleh pengguna umum, visualisasi ini sangat membantu programmer mengetahui kondisi data yang sedang dihadapi. Anggaplah seperti suatu alat debugging.</li>
<li>Ketika berurusan dengan data di dunia nyata, hampir dipastikan datanya ada kekotoran. Sudah tugas kita untuk membersihkan datanya sebelum langsung digunakan.</li>
<li>Tulislah program yang "CPU intensive" dalam bahasa pemrograman yang cocok!</li>
</ol>
Membuat aplikasi seperti ini menghabiskan banyak waktu. Namun semua itu menyenangkan untuk dilakukan, dan saya tidak menyesal ketika akhirnya tidak berhasil.</div>
<div>
<br /></div>
<div>
Pengalaman ini saya ceritakan sekedar untuk berbagai cerita saja tentang bagaimana data yang dihasilkan setiap hari dapat diolah dengan teknologi dan algoritma menjadi informasi yang berharga. Saya menganggap konsep itu sangat menarik.</div>
<div>
<br /></div>
<div>
Seluruh kode saya taruh di: <a href="https://bitbucket.org/account/user/terasindo/projects/TER">https://bitbucket.org/account/user/terasindo/projects/TER</a></div>
<div>
<br /></div>
<div>
Mungkin suatu saat nanti, kalau ada kesempatan saya dapat melakukan hal serupa untuk data lainnya. Barangkali data submission di TLX?</div>
William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com2tag:blogger.com,1999:blog-2894426456296176040.post-74207685867304656792019-03-19T10:30:00.000+07:002019-03-24T11:32:19.688+07:00Terasi (bagian 3): Agregasi, Database, dan Back End API<div style="display: none;">
<img border="0" data-original-height="365" data-original-width="525" height="222" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7Phl-104j6OT7JdK1Uun6zRRSXoKNLIbgoaiS-e58QpaV5IRd0tTGkX1k6fe37F5iQPyQR3Bie2UJiph1txRKpZ3E6aMsAFQGq05o3HusW2EivzupAOehx8ETcZMyjI5qYOwWA8Kll9sv/s320/g6857.png" width="320" /></div>
Sejauh ini, kita berhasil mendapatkan datanya. Kini waktunya untuk menghitung statistiknya dan membuat back end API sederhana. Nantinya bisa dibuat aplikasi sederhana (entah di HTML atau Android) untuk mengambil datanya dan menampilkan grafik waktu tunggu dan waktu tempuh.<br />
<br />
<hr />
<h3>
Agregasi Nilai Statistik</h3>
<div>
Data yang telah kita miliki adalah:<br />
<ol>
<li>Daftar ketibaan suatu bus, lengkap dengan:</li>
<ol>
<li>lokasi haltenya</li>
<li>halte tujuan akhirnya</li>
<li>waktu ketibaannya</li>
</ol>
<li>Daftar waktu tempuh suatu bus, lengkap dengan:</li>
<ol>
<li>halte awal</li>
<li>halte akhir</li>
<li>durasi</li>
</ol>
</ol>
<div>
Untuk prototipe pertama, informasi statistik berikut perlu didapatkan dengan bermodalkan data mentah di atas:</div>
<div>
<ol>
<li>Distribusi probabilitas dalam bentuk PDF (Probability Density Function) untuk waktu tunggu di suatu halte, ke halte tujuan, pada suatu waktu.</li>
<li>PDF untuk waktu tempuh bus antara setiap halte yang bertetanggaan.</li>
</ol>
</div>
</div>
Dari PDF, segala nilai seperti rata-rata, median, p95, dan lainnya yang nantinya dibutuhkan dapat dihitung dengan mudah. Strategi yang akan saya gunakan adalah memperkirakan PDF-nya dengan Kernel Density Estimation (KDE).<br />
<br />
<a name='more'></a><br /><br />
Konsepnya cukup sederhana. Bayangkan bahwa waktu tunggu (dalam menit) untuk 15 hari sebelumnya dari halte Glodok ke Blok M pada 06:00 adalah: [3, 2, 4, 2, 5, 3, 7, 4, 2, 1, 4, 6, 1, 2, 2].<br />
Apabila di-plot ke dalam histogram, bentuknya adalah:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjxiYsrlU-2oW4Sbtzqn2xDFCYsU3EQrxDShRSgKmjIh1XuSfX0MrOXKLP-qr_oOZw3UXVky-szFbhxFU5MHwwWXiV7-jEEaYt5NYpUGSxBU6nukSf1_E67wsuHmRcKeSg5ESxk7xMsXoi/s1600/g6830.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="365" data-original-width="525" height="222" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjxiYsrlU-2oW4Sbtzqn2xDFCYsU3EQrxDShRSgKmjIh1XuSfX0MrOXKLP-qr_oOZw3UXVky-szFbhxFU5MHwwWXiV7-jEEaYt5NYpUGSxBU6nukSf1_E67wsuHmRcKeSg5ESxk7xMsXoi/s320/g6830.png" width="320" /></a></div>
<br />
<br />
Saya membayangkan bahwa konsep dari KDE adalah "melelehkan" setiap balok dari batangan-batangan di histogram menjadi bentuk yang lebih halus. Bentuk lelehan ini haruslah memiliki luas yang sama dengan balok tersebut, yaitu 1 unit. Semua balok akan dilelehkan menjadi bentuk yang seragam. Ketika suatu batangan terdiri dari banyak balok, tentu saja lelehannya akan saling bertumpuk.<br />
<br />
Untuk penjelasan secara formal, silakan kunjungi Wikipedia: <a href="https://en.wikipedia.org/wiki/Kernel_density_estimation">https://en.wikipedia.org/wiki/Kernel_density_estimation</a>.<br />
<br />
Pada Terasi, bentuk lelehan yang akan saya gunakan adalah bentuk segitiga.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjroly3kul1BwRP5KiX2A_txzw4iqDhk6mVinaMDuVSqRlV-dEliuE71wSQUYMgoH9hc7o8qT6re1PScH4GOm9P_ws8akGm4MpW9od0jpG0C3-ZvaR5claaZyH37SzLGAnvyOrmLmA7-f9i/s1600/rect7028-6-2-8-5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="54" data-original-width="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjroly3kul1BwRP5KiX2A_txzw4iqDhk6mVinaMDuVSqRlV-dEliuE71wSQUYMgoH9hc7o8qT6re1PScH4GOm9P_ws8akGm4MpW9od0jpG0C3-ZvaR5claaZyH37SzLGAnvyOrmLmA7-f9i/s1600/rect7028-6-2-8-5.png" /></a></div>
<br />
Bayangkan apa yang terjadi apabila kita melelehkan seluruh balok penyusun histogram sebelumnya. Ingat bahwa lelehan ini masih "cair", sehingga lelehan yang bertumpuk akan turun. Berikut tampak akhirnya:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7Phl-104j6OT7JdK1Uun6zRRSXoKNLIbgoaiS-e58QpaV5IRd0tTGkX1k6fe37F5iQPyQR3Bie2UJiph1txRKpZ3E6aMsAFQGq05o3HusW2EivzupAOehx8ETcZMyjI5qYOwWA8Kll9sv/s1600/g6857.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="365" data-original-width="525" height="222" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7Phl-104j6OT7JdK1Uun6zRRSXoKNLIbgoaiS-e58QpaV5IRd0tTGkX1k6fe37F5iQPyQR3Bie2UJiph1txRKpZ3E6aMsAFQGq05o3HusW2EivzupAOehx8ETcZMyjI5qYOwWA8Kll9sv/s320/g6857.png" width="320" /></a></div>
<br />
<br />
Kini didapatkanlah PDF kasarnya.<br />
Perhitungan nilai lainnya menjadi sesederhana. Sebagai contoh, mencari median (=p50) sama dengan mencari nilai c sedemikian sehingga luas kurva dari x=0 sampai x=c sama dengan 0,50.<br />
<br />
Selesai memikirkan konsepnya, saya lanjut dengan menuliskan kode menghitung informasi statistik. Bahasa yang digunakan adalah Javascript dari Node.js. Berhubung data yang dimiliki adalah JSON, saya pikir pengolahannya akan mudah.<br />
<br />
Rutinitas untuk KDE saya buat dalam bentuk node modules di sini: <a href="https://www.npmjs.com/package/pdfast">https://www.npmjs.com/package/pdfast</a>.<br />
<br />
Sebenarnya perhitungannya cukup sederhana. Algoritmanya adalah:<br />
<ul>
<li>For all busstop x // untuk halte awal</li>
<ul>
<li>For all busstop d // untuk destinasi</li>
<ul>
<li>arrivals = get_past_arrivals(start=x, end=d, relative_past_day=15)</li>
<li>Urutkan semua data di "arrivals" berdasarkan waktu (jam, menit, detik)</li>
<li>Lakukan sliding window berdasarkan (jam, menit, detik) sambil mencari PDF-nya</li>
<ul>
<li>Sajikan PDF-nya dalam bentuk polyline, lalu hitung nilai statistik yang diperlukan dan cetak. Ini adalah hasil untuk halte awal x, halte tujuan d, dan waktu sesuai window di sliding window saat ini. Perhatikan gambar di bawah untuk lebih tepatnya.</li>
</ul>
</ul>
</ul>
</ul>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg73P8ENZEzj7p-4TbwibmK6FcDhFOLqvxzoaOQwxSRiNdpwQQRl3Bl8aHvirV2BKKrHnXp16oYpo9oGTDgqTBrSG4ADzjckfebTByCsFYnDsYznC0uBQwnYo_NA7yfIcT3hKkvyhgYKgPN/s1600/g7297-4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="521" data-original-width="500" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg73P8ENZEzj7p-4TbwibmK6FcDhFOLqvxzoaOQwxSRiNdpwQQRl3Bl8aHvirV2BKKrHnXp16oYpo9oGTDgqTBrSG4ADzjckfebTByCsFYnDsYznC0uBQwnYo_NA7yfIcT3hKkvyhgYKgPN/s400/g7297-4.png" width="383" /></a></div>
<br /></div>
<div>
<br /></div>
<div>
Mengapa menggunakan sliding window? Dugaan saya adalah waktu tunggu pukul 06:00 dengan 06:01, 06:02, dan seterusnya hingga 06:15 sepertinya tidak jauh berbeda. Memang akan ada penaikan atau penurunan, bergantung memasuki jam sibuk atau tidak. Jadi idenya adalah mari kita melakukan "bucketing" (pengemberan?), sehingga setiap M menit menjadi 1 ember. Nilai M yang saya gunakan adalah 20 menit. Jadi dari 06:00..06:20, waktu tunggunya dianggap sama semua. Jadi cukup cari waktu tunggu pada 06:00, 06:20, 06:40, 07:00, dan seterusnya. Bucketing ini membantu mengkompresi banyaknya data yang dihasilkan menjadi 1/M saja.<br />
<br /></div>
Hal yang sama juga saya lakukan untuk waktu tempuh.<br />
<br />
Karena sifat aktivitas ini adalah menggabungkan data beberapa hari sebelumnya menjadi beberapa nilai, saya akan menyebutnya dengan proses "agregasi".<br />
<br />
Pada akhirnya, didapatkanlah data yang saya inginkan. Apabila Anda tertarik, contohnya bisa dilihat di sini:<br />
<ol>
<li>Waktu tunggu: <a href="https://drive.google.com/open?id=1U-hHhI1MZas9NlRmfyYcXf4hcZBBllN4">https://drive.google.com/open?id=1U-hHhI1MZas9NlRmfyYcXf4hcZBBllN4</a></li>
<li>Waktu tempuh: <a href="https://drive.google.com/open?id=1rXTEQy8SNqx7JnCGXueQ0TblCWXKbVDc">https://drive.google.com/open?id=1rXTEQy8SNqx7JnCGXueQ0TblCWXKbVDc</a></li>
</ol>
<div>
Intepretasi salah satu data waktu tunggu untuk perjalanan dari Kota ke Blok M, pukul 06:00 dari berkas tersebut adalah:</div>
<ul>
<li>Rata-rata: 3 menit</li>
<li>Median: 3 menit</li>
<li>Persentil 90: 7 menit (pada 90% kasus, Anda hanya perlu menunggu paling lama 7 menit)</li>
</ul>
<div>
Angka yang sangat baik! Seandainya seluruh koridor memiliki layanan sebaik koridor 1...</div>
<br />
Oh ya, saya masih punya data untuk uji coba yang saya lakukan:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj14HVExShzq1W0cQFuvaUe6tOnGs-oH6QBspNUq2HUGJEsNCuT1ndWeXRAJgZ40FMBGu4uyPR9lFZFORjKmihAmcJuObXK6CSus6oyypxU_AWOO2UrrTZtwmR-JFijdWl27x1Cjh2kv8kK/s1600/pdf_sawah-besar_kota_1_12-00.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="429" data-original-width="838" height="203" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj14HVExShzq1W0cQFuvaUe6tOnGs-oH6QBspNUq2HUGJEsNCuT1ndWeXRAJgZ40FMBGu4uyPR9lFZFORjKmihAmcJuObXK6CSus6oyypxU_AWOO2UrrTZtwmR-JFijdWl27x1Cjh2kv8kK/s400/pdf_sawah-besar_kota_1_12-00.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">PDF untuk Sawah Besar - Kota, pukul 12:00. Jelas terlihat bentuk distribusi eksponensialnya<br />
(tinggi di awal, lalu menurun secara drastis dan penurunannya menjadi semakin pelan).</td></tr>
</tbody></table>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimpXI62Sm_88nDHLUcfO3ZXMFBbC-BcYn7fVaKJBi2l_NZJXbqbyVbePmUr8eCo7mB1vDMAAgtqYQAJmtT6-6bkt2lhw9QA-2vBNVgiI_IqFak69ZzJrQqOFEtMFOZ-R9JbZ3Ff-foTKP1/s1600/eta_sawah-besar_kota_1.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="395" data-original-width="733" height="215" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimpXI62Sm_88nDHLUcfO3ZXMFBbC-BcYn7fVaKJBi2l_NZJXbqbyVbePmUr8eCo7mB1vDMAAgtqYQAJmtT6-6bkt2lhw9QA-2vBNVgiI_IqFak69ZzJrQqOFEtMFOZ-R9JbZ3Ff-foTKP1/s400/eta_sawah-besar_kota_1.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Waktu rata-rata menunggu bus di Sawah Besar untuk ke Kota, dari 00:00 sampai 23:59.<br />
Terlihat bahwa bus aktif beroperasi mulai pukul 5 sampai pukul 23. </td></tr>
</tbody></table>
<br />
Proses agregasi data ini cukup dilakukan untuk satu hari, yang akan mengagregasi data 15 hari sebelumnya. Untuk ke depannya, bisa dibuat script untuk menjalankan agregasi setiap 00:00, untuk merotasi hari-hari yang dilibatkan dalam agregasinya (yang paling lama dikeluarkan, data seharian yang baru dimasukkan).<br />
<br />
Kini angka-angka itu siap digunakan sebagai modal untuk perhitungan turunannya, misalnya meramalkan total waktu perjalanan dari suatu halte ke halte lainnya pada jam tertentu.<br />
<br />
<hr />
<h3>
Pemodelan untuk Database</h3>
<div>
Dari pemanenan data mentah, diekstraksi, lalu "dimasak" menjadi data statistik yang matang. Kini saatnya data disimpan untuk nantinya disajikan ke aplikasi.<br />
<br />
Arsitektur yang klasik untuk back end API adalah:<br />
<ol>
<li>Sebuah database yang menyimpan data</li>
<li>Sebuah web server yang menerima request, lalu membalasnya dengan jawaban yang diminta.<br />
Dalam kasus Terasi, jawabannya didapatkan dari database.</li>
</ol>
</div>
<div>
Apakah saya perlu database khusus? Seperti PostgreSQL, MySQL, atau sejenisnya?<br />
<br />
Untuk menjawabnya, perlu dilihat dulu pola data Terasi. Polanya adalah:<br />
<ol>
<li>Setiap harinya, akan ada sebuah aktivitas "daur ulang" data yang berupa:</li>
<ol>
<li>Menghapus seluruh informasi waktu tunggu dan waktu tempuh</li>
<li>Memasukkan seluruh informasi waktu tunggu dan waktu tempuh yang baru saja diagregasi</li>
</ol>
<li>Selain yang ada pada aktivitas "daur ulang", tidak akan ada operasi pengubahan data</li>
<li>Operasi pembacaan data dipastikan hanya akan membaca 1 titik data (tidak pernah range query).</li>
<li>Ukuran data cenderung tetap, kecuali ditambahkan koridor atau halte baru yang selama ini jarang terjadi.</li>
</ol>
</div>
<div>
Ternyata polanya sangat sederhana. Saya mempertimbangkan untuk menggunakan salah satu dari dua cara berikut:<br />
<ol>
<li>In memory database. Maksudnya adalah data cukup disimpan dalam berkas .txt saja. Lalu saat web server dinyalakan, web server akan membacanya dan menyimpannya dalam RAM dalam bentuk array. Dengan cara ini, seluruh operasi menjadi O(1) dan saya tidak perlu pusing dengan I/O. Syarat untuk bisa menggunakannya adalah RAM server cukup untuk menampung seluruh data.</li>
<li>Database "sederhana" seperti MongoDB. Berhubung saya pernah menggunakannya. Rasanya cukup mudah dan fleksibel. Waktu itu sedang nge-tren, jadi boleh saja deh dicoba-coba. Operasinya menjadi tidak O(1) lagi, tetapi O(log N) dengan pengindeksan data yang benar. Kompleksitas logaritmik itu berasal dari B-Tree dalam MongoDB.</li>
</ol>
<div>
Setelah hitung-hitung memori, in memory database dapat digunakan.</div>
<div>
<br /></div>
<hr />
<h3>
Struktur Back End API</h3>
</div>
<div>
Inilah bagian penyajian data yang telah disimpan.<br />
<br />
Untuk keperluan prototipe, saya hanya perlu menyediakan 2 end point:<br />
<ol>
<li>GET /waiting-time/:startBusStop/:endBusStop/:time</li>
<li>GET /travel-time/:startBusStop/:endBusStop/:time</li>
</ol>
</div>
<div>
... dan selesai.<br />
Itu pun sekedar melihat data yang disimpan dalam array saja. Jadi seharusnya selesailah pembuatan back end ini.<br />
<br />
Tapi ternyata tidak semudah itu. Hitung-hitungan memori saya ternyata tidak tepat. Karena back end diimplementasikan dengan Node.js, ternyata konsumsi memorinya tidak mudah diprediksi. Misalnya kita membuat array bilangan integer 32 bit sebesar N. Ukuran memori yang digunakan tidak semata-mata (sekitar) 4*N byte. Memang tidak mungkin sama dengan segitu, karena ada block size, paging, atau sejenisnya pada penyimpanan hard disk. Tetapi kurang lebih mestinya mirip. Pada kasus Node.js, ternyata memorinya meledak-ledak, sampai komputer saya thrashing.<br />
<br />
Mungkin saya bisa hardcore untuk membuat web server dengan C++? Atau bahasa pemrograman yang bertipe data statis lainnya? Yang tidak perlu garbage collection? Karena tidak berhasil menemukan framework atau bahasa yang tepat, akhirnya saya memutuskan untuk menggunakan opsi database kedua, yaitu MongoDB.<br />
<br />
Sekitar dua hari menulis script untuk memasukkan data ke database dan membuat back end API, akhirnya selesailah.<br />
<br />
<hr />
<h3>
Langkah Selanjutnya</h3>
</div>
<div>
Sejauh ini, berikut diagram aliran data yang dimulai dari API Transjakarta sampai digunakan di back end.<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhZgMi2ze6NFMzl7Ro-W9Q4DwcArLOEIPO88CJOyGJeobYpDDY1fXIWZP3TcJujlyQLoK20V8OsFd8-ftA2lhG9ulGh9EC0GPWtNWxqffz2LS5jpYhMrD7cNR3Tr7RBHbLHYVKSXNvAy_V/s1600/g7624.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="314" data-original-width="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhZgMi2ze6NFMzl7Ro-W9Q4DwcArLOEIPO88CJOyGJeobYpDDY1fXIWZP3TcJujlyQLoK20V8OsFd8-ftA2lhG9ulGh9EC0GPWtNWxqffz2LS5jpYhMrD7cNR3Tr7RBHbLHYVKSXNvAy_V/s1600/g7624.png" /></a></div>
<br />
Beberapa keterangan untuk menyimpulkan:<br />
<ul>
<li>Pemanen bekerja setiap menit, menghasilkan snapshot lokasi setiap bus setiap menitnya</li>
<li>Graph Transjakarta dihasilkan oleh manusia yang secara manual menuliskan hubungan halte + rute berikut polyline-nya.</li>
<li>Pengekstrak dan pengagregat data akan bekerja setiap malam, memproses data yang telah didapatkan hari itu dan menghasilkan data waktu tunggu dan waktu tempuh. Hasil agregasi ini dimuat ke dalam database.</li>
<li>Back end API membaca data dari database, dan menjadi dependensi dari front end (entah web, atau apps hp) & pengguna.</li>
</ul>
Sebagian besar pemrosesan data ini berupa batch processing.<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMdaHxIztLm1bdRD_Qs2WrBas4mVo9knuAMnsKtVYxYrbUZYXnO0i8NNS0QG7AbS-yMQ2oaedpQspGASSwxG4e8rl8Bn8FldFPbX_AjVBHePGju_8xL8_l4ogR7Oid3bKhv7coWUTr7FcF/s1600/batch-please.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="222" data-original-width="157" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMdaHxIztLm1bdRD_Qs2WrBas4mVo9knuAMnsKtVYxYrbUZYXnO0i8NNS0QG7AbS-yMQ2oaedpQspGASSwxG4e8rl8Bn8FldFPbX_AjVBHePGju_8xL8_l4ogR7Oid3bKhv7coWUTr7FcF/s1600/batch-please.png" /></a></div>
<br />
Urusan data sudah selesai. Selanjutnya adalah pembuatan front end.</div>
<div>
Saya menghabiskan 1 hari untuk belajar D3.js (<a href="https://d3js.org/">https://d3js.org/</a>) untuk membuat halaman HTML + ajax yang mengambil data dari back end, lalu menampilkan grafik. Sekedar untuk keperluan melihat-lihat data dan mewaspadai anomali. Tentu saja, kalau waktu tunggu dari suatu halte ke halte lainnya mencapai berjam-jam, berarti ada kekacauan dalam algoritma yang digunakan.</div>
<div>
<br /></div>
<div>
Hasilnya terlihat menjanjikan, sehingga tahap selanjutnya adalah pembuatan aplikasi Androidnya. Saya menghabiskan waktu 2 minggu untuk belajar Android apps development di Udacity (<a href="https://www.udacity.com/">https://www.udacity.com/</a>) secara gratis.</div>
<div>
<br /></div>
<div>
Kemudian langsung lanjut membuat UI sederhana yang asal fungsionalitasnya bisa dicoba. Saya berhasil membuat search bar nama halte, pemilihan waktu, dan baru menampilkan data waktu tunggunya saja.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgymSWtxfX65K85Fs0xCaVx2W6QOZ5xdPTly-DMuQx1vfUcEotqgb05LFVXQaevJ0PlaCP_uLQXlTQqAuMsfp3nXUYAFZkDa8X2OMczdVnjX-RQ6bOFCsQi3jixft9Voe9bIiOvn77JqIdP/s1600/image3667.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="315" data-original-width="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgymSWtxfX65K85Fs0xCaVx2W6QOZ5xdPTly-DMuQx1vfUcEotqgb05LFVXQaevJ0PlaCP_uLQXlTQqAuMsfp3nXUYAFZkDa8X2OMczdVnjX-RQ6bOFCsQi3jixft9Voe9bIiOvn77JqIdP/s1600/image3667.png" /></a></div>
<div>
<br /></div>
<div>
Tulisan mendatang akan menjelaskan kelanjutan dari proyek ini.</div>
<div>
<br /></div>
<br />William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-73190183036297483042019-03-05T13:15:00.000+07:002019-03-05T14:39:20.863+07:00Terasi (bagian 2): Panen dan Pengolahan DataSetelah membahas bagaimana ide dasar proyek Terasi muncul, tulisan ini akan membahas pengolahan data yang dibutuhkan.<span id="goog_1964928622"></span><br />
<br />
<hr />
<h3>
Jakara Smart City API</h3>
Dengan googling sekilas, saya menemukan bahwa Jakarta Smart City memang membuka API untuk Transjakarta. Terdapat data untuk halte-halte dalam suatu koridor, koordinat halte, dan koordinat setiap bus pada saat itu juga. Saya tidak menemukan data lain yang lebih berguna, jadi diputuskanlah untuk menggunakan koordinat setiap bus pada suatu waktu.<br />
<br />
API untuk koordinat setiap bus dapat diakses di <a href="http://202.51.116.138:8088/jsc_tj_api.php">http://202.51.116.138:8088/jsc_tj_api.php</a>. API itu sekarang sudah tewas, tetapi sebelumnya dia akan memberikan JSON berisi koordinat setiap bus dalam format:<br />
<div>
<script class="brush: js" type="syntaxhighlighter"><![CDATA[
{
"buswaytracking": [
{
"buscode": "BMP 002",
"gpsdatetime": "2016-01-25 23:59:55",
"koridor": "9",
"longitude": 106.874809,
"latitude": -6.25293,
"speed": 0,
"course": 285
},
{
"buscode": "BMP 003",
"gpsdatetime": "2016-01-25 18:11:47",
"koridor": "9",
"longitude": 106.876259,
"latitude": -6.25313,
"speed": 0,
"course": 0
},
{
"buscode": "BMP 007",
"gpsdatetime": "2016-01-25 23:59:54",
"koridor": "10",
"longitude": 106.874718,
"latitude": -6.25286,
"speed": 0,
"course": 106
}
]
}
]]></script><!-----></div>
Saya hanya menampilkan tiga bus saja, tetapi sebenarnya ada 461 bus pada 26 Januari 2016. Sekali mengkonsumsi API itu, didapatkan data sekitar 60 kB. Untuk keperluan Terasi, saya akan mengkonsumsi API itu setiap 1 menit sekali, sehingga setiap hari didapatkan 24 * 60 berkas. Jika 1 berkas berukuran 60 kB, berarti setiap harinya saya memanen data sebesar 86,4 MB. Dalam 1 bulan, datanya akan sebesar 2,6 GB. Angka ini terlihat besar, tapi sebenarnya data JSON yang diberikan ini dapat dikompres menjadi data tabular (misal: csv) yang lebih ringkas. Karena itu saya tidak mengkhawatirkan ukuran datanya.<br />
<br />
Dengan "snapshot" koordinat bus setiap menitnya, kita dapat mensimulasikan pergerakan bus secara cukup akurat.<br />
<br />
<hr />
<h3>
Pemanen Data</h3>
Selanjutnya saya menuliskan program sederhana dengan Node.js untuk memanggil API tersebut setiap menitnya, lalu menyimpan JSON yang diberikan. Setiap akhir hari, data yang terkumpul dikompres menjadi .tar.gz. Kalau ditanya kenapa pakai Node.js, karena kebetulan saya sedang belajar itu.<br />
<br />
Karena saya tidak mau menyalakan komputer sepanjang waktu, saya menyewa server melalui DigitalOcean. Dengan biaya 5 dollar AS setiap bulannya, saya mendapat server paling murah dengan spesifikasi lebih lemah dari laptop tua saya. Servernya memiliki RAM 512 MB dan hard disk 25 GB. Meskipun lemah, cukup untuk keperluan pemanenan data ini.<br />
<br />
Untuk memastikan bahwa data terus menerus dipanen, saya membuat sistem logging sederhana. Kalau untuk alasan apapun API memberikan error atau data yang rusak, maka error akan dicatat dan dilaporkan ke channel gratisan Slack (<a href="https://slack.com/">https://slack.com/</a>). Saya cukup memasang aplikasi Slack di hp saya, lalu setiap notifikasi error dapat segera diketahui. Untuk menjaga kesehatannya, setiap hari akan ada laporan berapa ukuran data yang dipanen hari itu juga. Jadi kalau ukuran datanya tidak normal, error bisa disampaikan juga.<br />
<br />
<span id="goog_2079411043"></span> Sampai saat ini, saya rasa pemanen datanya sudah bekerja. Selanjutnya perlu dipastikan bahwa data yang diberikan API ini masuk akal. Kalau misalnya posisi busnya sering tidak diperbaharui, atau koordinatnya bisa kacau, dijamin hasil akhirnya amburadul. Oleh karena itu saya terpikir untuk menulis program sederhana untuk memvisualisasikan data perjalanan bus tersebut seperti sebuah video.<br />
<br />
<hr />
<h3>
Visualisasi</h3>
Setelah pulang kerja keesokan harinya, saya memeriksa data yang telah dipanen. Terlihat baik, karena setiap menitnya ada data dan tidak ada kerusakan data.<br />
<br />
Lalu saya menghabiskan 2 jam berurusan dengan Java awt sambil mengingat kembali ilmu kuliah semester 1. Akhirnya berhasillah penulisan program untuk menggambarkan pergerakan bus berdasarkan API.<br />
<br />
Hasilnya memuaskan. Berikut gambaran bus dari 00:00 sampai 23:59. Terlihat bahwa dari 00:00 sampai 03:00 pergerakan bus hanya di koridor tertentu. Masuk akal, berhubung tidak semua bus beroperasi tengah malam. Sesudah itu bus mulai ramai (jam masuk kerja), terutama sejak 05:00. Selanjutnya saya menyaring hasilnya supaya hanya menunjukkan bus dengan label koridor "1".<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiP9I1Ph85slwliS2Ak7nIjV9QVeLXT02EGRA59c9mvNBkX7co4NxYVFAvItbeyfO6SWO6zYWWBo8IUw67uynDe5c-sXjkja6Do_2BMrvINg4lQByfPvA6GIv8SdlCcn5095knXVqXMawDo/s1600/visualizer.gif" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="240" data-original-width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiP9I1Ph85slwliS2Ak7nIjV9QVeLXT02EGRA59c9mvNBkX7co4NxYVFAvItbeyfO6SWO6zYWWBo8IUw67uynDe5c-sXjkja6Do_2BMrvINg4lQByfPvA6GIv8SdlCcn5095knXVqXMawDo/s1600/visualizer.gif" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Video beresolusi tinggi bisa dilihat di <a href="https://youtu.be/lzyAfKbAiwc">https://youtu.be/lzyAfKbAiwc</a></td></tr>
</tbody></table>
<br />
<a name='more'></a><br /><br />
Ketika diteliti, terdapat kejanggalan. Beberapa bus mengaku sedang berada di suatu koridor, tapi ternyata sedang berkeliaran di koridor lainnya. Berikut cuplikan bus-bus yang mengaku di koridor 1 (merah), tetapi berjalan di koridor 3 (biru) atau 5 (hijau). Hal ini juga terjadi di koridor-koridor lainnya.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIEGkLc0ehJ138JqsyYUipV0fv_8zDKwPfr7L4rKVJkffO9omsX3ulnki2Z1QzkEAfAtKyFmqnN6s9n_Cy0r41qey-ugJNCGD4VctzpU5_YnYxuUXY4Nv84jKOb3RyRQC2ahy7aUtjNKSI/s1600/terasi-polusi.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="404" data-original-width="562" height="287" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIEGkLc0ehJ138JqsyYUipV0fv_8zDKwPfr7L4rKVJkffO9omsX3ulnki2Z1QzkEAfAtKyFmqnN6s9n_Cy0r41qey-ugJNCGD4VctzpU5_YnYxuUXY4Nv84jKOb3RyRQC2ahy7aUtjNKSI/s400/terasi-polusi.png" width="400" /></a></div>
<br />
Saya rasa ada semacam GPS yang dipasang di setiap bus, yang secara periodik mengirimkan data lokasi ke server, berikut informasi ID bus dan koridor yang sedang dilalui. Tapi karena bus-bus mungkin dialihkan ke koridor lain pada jam tertentu, dan barangkali informasi koridornya perlu diganti secara manual dan terlupakan, didapatkanlah data yang tidak bersih ini.<br />
<br />
Berkat program visualisasi ini, dapat disimpulkan:<br />
<ol>
<li>TJ beroperasi 24 jam, meskipun tidak semua koridor beroperasi pada jam malam.</li>
<li>Beberapa bus akan "bertengger" di suatu lokasi (misal: selatan Jakarta) saat sedang tidak beroperasi. Mungkin itu semacam bengkel?</li>
<li>Bus bisa saja berpindah koridor. Pada pagi hari di koridor A, lalu sore hari di koridor B.</li>
<li>Bus mungkin saja bergerak di jalan yang bukan trayek transjakarta. Mungkin sedang berpindah koridor.</li>
<li>Informasi koridor tidak bisa dipercaya, artinya saya harus menebak di mana suatu bus berada berdasarkan posisinya.</li>
</ol>
<br />
<hr />
<h3>
Ekstraksi Data</h3>
<div>
Setelah data dipanen dan dimengerti, kini saatnya mengolah data tersebut. Untuk memenuhi kebutuhan perhitungan, saya perlu mengubah data koordinat bus setiap menit menjadi 2 macam informasi:</div>
<div>
<ol>
<li>Pada pukul T, bus dengan suatu ID tiba di halte A, dan diketahui halte terakhir yang akan dicapai adalah B. Bus ini sedang melayani koridor-koridor K1, K2, K3, .... (suatu halte bisa digunakan oleh beberapa koridor).</li>
<li>Pada pukul T1, bus dengan suatu ID berpindah dari halte A, dan tiba di halte B pada pukul T2. Dijamin A dan B adalah halte yang bertetangga. </li>
</ol>
</div>
Untuk menghitung nilai-nilai tersebut, cara yang terpikirkan adalah melakukan segmentasi terhadap perjalanan bus. Misalnya kita sedang berurusan pada bus di koridor 1 (Kota - Blok M). Cari tahu kapan waktu bus berangkat dari Kota dan tiba di blok M, atau sebaliknya. Perhitungan ketibaan bus dapat dihitung dengan fokus pada segmen ini.<br />
<br />
Misalkan diketahui berangkatnya pukul 06:00 dan tiba pukul 07:50 (ini hanya angka asal-asalan). Dengan informasi posisi setiap halte dalam koridor tersebut dan rekam jejak posisi bus setiap menitnya, kita bisa menggunakan interpolasi linier untuk memperkirakan kapan bus tiba di setiap halte-halte dalam segmen tersebut.<br />
<br />
Idenya adalah "meluruskan" polyline halte-halte dan rekam jejak bus ke dalam garis lurus, lalu dari sana bisa diperkirakan jam, menit, dan detiknya suatu bus sampai di setiap halte. Tulisan dicetak biru pada gambar di bawah adalah nilai yang hendak kita dapatkan. "Pelurusan" polyline dilakukan dengan memproyeksikan polyline rekam jejak bus ke polyline trayek bus.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5m9mQzLUassmPrcq3p8zPwQ0N4Ozau2UWDTrM4YFb_16s_0n0-pOVfDIwW08TqEaTFHJfwieSKQJAhtkc3h7WMJ4fEgJi1Z4jjxqWuMc67W5OchoLj2bI2DV9B8v5GiBUQn-0VFHEYzLC/s1600/rect7028-6-9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="491" data-original-width="600" height="450" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5m9mQzLUassmPrcq3p8zPwQ0N4Ozau2UWDTrM4YFb_16s_0n0-pOVfDIwW08TqEaTFHJfwieSKQJAhtkc3h7WMJ4fEgJi1Z4jjxqWuMc67W5OchoLj2bI2DV9B8v5GiBUQn-0VFHEYzLC/s1600/rect7028-6-9.png" width="550" /></a></div>
<br />
<br />
Namun cara ini hanya bekerja apabila kita dapat menyelesaikan 2 permasalahan:<br />
<ol>
<li>Tahu kapan suatu bus memulai perjalanan (awal segmen) dan mengakhiri perjalanan (akhir segmen).</li>
<li>Mencari tahu di koridor mana bus ini sedang berjalan, penting untuk mengetahui dasar polyline mana yang perlu digunakan untuk sasaran proyeksinya.</li>
</ol>
<div>
Mari kita bahas satu per satu.</div>
<div>
<br /></div>
<div>
<h4>
Proses Segmentasi</h4>
</div>
<div>
</div>
Pencarian awal dan akhir segmen cukup sederhana. Setelah saya menonton pergerakan bus-bus dan mencoba menulis program awal, ditemukan tiga ciri-ciri suatu bus mencapai awal/akhir segmen:<br />
<ol>
<li>Bus menghabiskan waktu yang lama (>3 menit) di halte bus yang merupakan ujung suatu koridor.</li>
<li>Bus melakukan U-turn, artinya dia mendekati halte B, lalu ke halte A, lalu ke halte B lagi. Ada pengecualian untuk koridor 12 yang mana trayeknya memiliki suatu U-turn.</li>
<li>Bus mencapai suatu halte ujung koridor, lalu keluar dari trayek bus dan pergi jauh entah ke mana (mungkin bengkel, atau bergerak melayani koridor lainnya).</li>
</ol>
<br />
Kalau kita tahu ujung-ujung segmen (tanpa peduli itu awal atau akhir), cukup dipotong-potong saja. Akhir suatu segmen pastilah awal segmen berikutnya.<br />
<br />
<h4>
Proses Pencocokan Halte (polyline matching?)</h4>
Sejauh ini kita menyelesaikan masalah pertama. Masalah kedua ini lebih sulit, karena kita harus mencocokkan polyline dengan polyline koridor. Parahnya saya tidak punya data polyline suatu koridor. Yang ada hanya koordinat setiap halte saja dan itu tidak cukup akurat untuk memodelkan rute TJ.<br />
<br />
Oleh karena itu saya perlu kembali menonton visualisasi yang dibuat, lalu secara manual mengambil sampel bus yang berada di koridor tertentu. Kemudian catat kapan busnya berangkat dari halte pertama, dan kapan busnya sampai halte terakhir. Karena jalan yang dilalui tidak selalu simetris antara "pergi" dan "pulang" (misal: Kota - Blok M dan Blok M - Kota), cara ini perlu diulang untuk kepergian dan kepulangan. Setelah dicatat, tinggal jalankan program untuk mengambil semua titik koordinat yang dilalui bus, lalu lakukan smoothing. Algoritma smoothingnya sesederhana membuang titik yang sangat dekat dengan titik sebelumnya, dan membuang tiga titik yang co-linear. Cukup melelahkan untuk melakukannya bagi seluruh koridor dan kedua arahnya. Untungnya ini hanya perlu dilakukan satu kali saja.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVevYgPaxy9NCnuH3cEi7BOeVoGCxXWlQJ8mfRXLtCnb_kMDeWkFVqn4EL1uAVZ5PN89CwAkImFSIiUm_D-ABta-uuc1T4in0gB7l0sVjcIScf3lO-ubOh0yHGa3cHQJqEIMgy8letk3jf/s1600/rect7028-6-9-3.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="481" data-original-width="500" height="307" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVevYgPaxy9NCnuH3cEi7BOeVoGCxXWlQJ8mfRXLtCnb_kMDeWkFVqn4EL1uAVZ5PN89CwAkImFSIiUm_D-ABta-uuc1T4in0gB7l0sVjcIScf3lO-ubOh0yHGa3cHQJqEIMgy8letk3jf/s320/rect7028-6-9-3.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Smoothing polyline untuk koridor 1</td></tr>
</tbody></table>
<br />
Manfaat dari smoothing polyline ini:<br />
<ul>
<li>Mengurangi "segmen garis" yang memiliki panjang 0. Hal ini terjadi ketika bus tidak bergerak selama beberapa menit. Garisnya kini cacat, dan terlihat seperti titik. Kasus cacat bisa menimbulkan kasus khusus (special case) untuk algoritma interpolasi, berhubung akan banyak hitung-hitungan geometrinya. Saya sudah bisa mencium bau-bau "division by zero" saat menghitung proyeksi titik ke segmen garis dengan panjang 0.</li>
<li>Banyaknya titik dalam polyline menjadi berkurang, dari ratusan kini hanya belasan saja. Mengerjakan soal dengan batasan N kecil pastinya lebih mudah daripada N besar.</li>
</ul>
<br />
Selanjutnya adalah bagaimana mencocokkan polyline suatu segmen dengan koridor. Cara yang saya gunakan adalah brute force, mencoba semua koridor dan mencari yang paling cocok. Kecocokan didefinisikan dengan jarak terjauh dari jarak terdekat antara titik segmen dengan titik polyline koridor. Semakin kecil, artinya semakin cocok. Perlu diketahui bahwa suatu koridor bisa saja merupakan subhimpunan dari koridor lainnya, sehingga suatu segmen bisa saja cocok dengan lebih dari satu koridor.<br />
<br />
Apabila ketibaan ditemukan, kita cukup mengurutkan ketibaan di dalam suatu segmen, dan rentang waktu antar 2 ketibaan akan menjadi data waktu tempuh. Jadi berhasillah ekstraksi data ketibaan dan waktu tempuh dari data log posisi bus.<br />
<br />
<hr />
<h3>
Implementasi</h3>
Setelah idenya jelas, saya mulai menuliskan program untuk mengekstraksi ketibaan. Banyak kasus-kasus yang perlu diperhatikan berhubung datanya tidak selalu bersih. Misalnya bus bisa saja tidak memberikan data selama beberapa menit, atau bus tiba-tiba kehilangan koneksi. Saya menghabiskan beberapa hari untuk menuliskan kodenya dalam Node.js, berhubung pengolahan JSON mudah dilakukan dengan Javascript. Sambil coding, saya menyadari bahwa lagu Trance membantu fokus selama coding sampai lupa waktu. Rasanya saya memasuki sebuah kondisi "coding trance". Ini contohnya: <a href="https://www.youtube.com/watch?v=fY3D2VpPRkE">https://www.youtube.com/watch?v=fY3D2VpPRkE</a><br />
<br />
Akhirnya selesailah program ekstraksi datanya dan dapat dihasilkan:<br />
<div>
<script class="brush: js" type="syntaxhighlighter"><![CDATA[
// Ketibaan
[
{
"busId": "BMP 003",
"busStop": 97,
"route": 8,
"destinationBusStop": 58,
"arrivalTime": 1462143180560,
"time": "2016-05-02 05:53:00"
},
{
"busId": "BMP 003",
"busStop": 148,
"route": 8,
"destinationBusStop": 58,
"arrivalTime": 1462143180560,
"time": "2016-05-02 05:53:00"
},
{
"busId": "BMP 003",
"busStop": 146,
"route": 8,
"destinationBusStop": 58,
"arrivalTime": 1462143238379,
"time": "2016-05-02 05:53:58"
}
]
]]></script><!-----></div>
<div>
<script class="brush: js" type="syntaxhighlighter"><![CDATA[
// Waktu tempuh
[
{
"busId": "BMP 003",
"from": 97,
"to": 148,
"deltaSecond": 0,
"time": "2016-05-02 05:53:00",
"startTime": 1462143180560,
"startThSecond": 21181,
"thDay": 16923
},
{
"busId": "BMP 003",
"from": 148,
"to": 146,
"deltaSecond": 58,
"time": "2016-05-02 05:53:00",
"startTime": 1462143180560,
"startThSecond": 21181,
"thDay": 16923
},
{
"busId": "BMP 003",
"from": 146,
"to": 147,
"deltaSecond": 301,
"time": "2016-05-02 05:53:58",
"startTime": 1462143238379,
"startThSecond": 21238,
"thDay": 16923
}
]
]]></script><!-----></div>
<br />
Saya hanya menampilkan 3 datum saja. Sebenarnya terdapat 48372 ketibaan pada hari itu. Secara mengejutkan banyak juga ketibaannya. Kalau kalian tertarik, datanya bisa diunduh:<br />
<br />
<ul>
<li>ketibaan: <a href="https://drive.google.com/open?id=1L0kSbR4kol-ZbAQzPhZYjcOqs6TEtjXY">https://drive.google.com/open?id=1L0kSbR4kol-ZbAQzPhZYjcOqs6TEtjXY</a></li>
<li>waktu tempuh: <a href="https://drive.google.com/open?id=1XLarDeey9FbsGWT9Bwd6Xzl4V2A6FsAq">https://drive.google.com/open?id=1XLarDeey9FbsGWT9Bwd6Xzl4V2A6FsAq</a></li>
</ul>
<hr />
<h3>
Langkah Selanjutnya</h3>
Kini data telah didapatkan. Saya tinggal melakukan ekstraksi data untuk beberapa hari untuk dijadikan bahan percobaan dalam perhitungan nilai statistiknya. Apabila Hitung-hitungannya sudah masuk akal, berarti sudah waktunya diimplementasikan dalam bentuk back end API.<br />
<br />
Dari perjalanan kali ini, dipelajari bahwa pembuatan visualisasi sangat penting. Program itu membantu saya men-debug, terutama untuk mencari tahu apa yang terjadi saat ada anomali program ekstraksi data.<br />
<br />
Berhubung tulisan ini sudah panjang, langkah ini akan saya bahas pada kesempatan berikutnya.<br />
<br />William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-543267469789274072019-02-11T20:05:00.000+07:002019-02-11T20:05:00.286+07:00Terasi (bagian 1): Proyek Analitik Transjakarta<div style="display: none;"><img border="0" data-original-height="300" data-original-width="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiz6QVYaUsI6Y4I4GTBLgvQbVycvMdSqWFl73nomkTI1iVYS6RhNFzt9R4ssMqQwg9eUAb_cev7gW3RwEj-36q027fRfscOTqUrF8rJeHq61wpiKwFf7ZKQmP6hA3VczI7M8sTFN_qLFx_v/s1600/46455976502_0f485b10b3_z.jpg" /></div><div dir="ltr" style="text-align: left;" trbidi="on">Seri tulisan kali ini tidak ada hubungannya langsung dengan pemrograman kompetitif. Namun, saya menggunakan sejumlah ilmunya dalam perjalanan yang akan saya ceritakan berikut.<br />
<br />
Setelah lulus dari perguruan tinggi, saya kerja di <a href="https://www.cermati.com/">Cermati</a>. Kantornya di dekat mall Central Park, Jakarta Barat. Kebetulan lokasinya sangat strategis, terdapat halte Transjakarta di dekatnya. Saya pikir untuk pergi dan pulang ke kantor menjadi sesederhana naik Transjakarta saja. Namun sebenarnya tidak sesederhana itu...<br />
<br />
Waktu itu adalah tahun 2015. Saya tidak tahu apakah sekarang situasinya sudah membaik, tetapi kondisi lalu lintas tahun itu cukup mengenaskan. Daerah Jalan S. Parman terkenal sangat macet pada jam masuk atau pulang kantor. Awalnya saya pikir ada busway (jalur Transjakarta), yang membuat Transjakarta kebal kemacetan. Sayangnya, ternyata di Jalan S. Parman terdapat penyatuan busway dengan jalan raya yang membuat bus ikut kena macet...<br />
<br />
Akibat dari kemacetan ini adalah:<br />
<ol style="text-align: left;"><li>Pergerakan bus menjadi lambat, sehingga...</li>
<li>Frekuensi kedatangan bus menjadi rendah, sehingga...</li>
<li>Orang yang mengantre di halte menjadi ramai, sehingga...</li>
<li>Begitu bus sampai di halte, tidak semua orang kedapatan menaiki bus, sehingga...</li>
<li>Harus menunggu kedatangan bus berikutnya, sehingga...</li>
<li>... tekanan darah saya meningkat karena perjalanan dari rumah ke kantor menjadi lama.</li>
</ol><div>Seluruh akibat itu saya rasakan setiap hari, ditambah dengan setiap kali naik TJ (Transjakarta) untuk jalan-jalan di Jakarta. Hingga suatu ketika saya muak, dan menjadi marah. Bagaimana mungkin fasilitas umum untuk masa depan beroperasi seperti ini?<br />
<br />
Akhirnya energi amarah yang meluap-luap itu saya alirkan ke ide untuk membuat sistem yang dapat menghitung metrik-metrik Transjakarta, seperti waktu tunggu, waktu perjalanan dari halte ke halte, rata-rata kedatangan bus, dan sebagainya. Dengan metrik tersebut, saya dapat mengukur apakah untuk suatu perjalanan saya akan menggunakan Transjakarta. Apabila ternyata data menunjukkan perjalanannya akan memakan waktu lama, lebih baik saya naik ojek.</div><a name='more'></a><br />
Sebagai bonus, barangkali saya bisa menghasilkan semacam laporan yang berisi "efisiensi" TJ. Bayangannya adalah tabel berisi seluruh halte dan trayek tujuan, berikut waktu tunggu rerata kedatangan bus. Tidak hanya itu, berbagai informasi lainnya juga bisa diturunkan. Laporan ini bisa disampaikan ke pihak TJ, pemerintah daerah, atau pihak yang bertanggung jawab atas lalu lintas. Kemudian mereka bisa menggunakannya untuk mengetahui situasi dan mengambil keputusan. Seperti:<br />
<ul><li>"oh ternyata waktu tunggu di daerah Bendungan Hilir jam 6 sore mencapai 40 menit, mungkin sebaiknya kita tambahkan bus di sana"</li>
<li>"banyaknya bus yang beroperasi di koridor 11 ternyata tinggal 60% dari yang awalnya diatur, dikarenakan bus-busnya yang mulai rusak dan tidak beroperasi lagi. Saatnya kita remajakan"</li>
<li>"waktu tempuh antar halte di jalur Slipi jam 7 pagi sangat parah, ada apa gerangan? Apakah jalur busway di sana tidak steril karena kendaraan lain? Mungkin saatnya kita pasang CCTV di sana untuk mencatat plat nomor kendaraan-kendaraan liar itu, lalu kita denda"</li>
</ul><div>Yah tentunya masih banyak yang bisa dilakukan kalau kita punya datanya. Berhubung saya sudah pensiun pemrograman kompetitif, sepertinya ide ini bisa diwujudkan sebagai proyek sampingan.</div><div><br />
Saya juga memeriksa apakah sudah ada aplikasi yang serupa. Untungnya belum ada.<br />
<br />
</div><hr /><h3>Formulasi Ide</h3><div>Dasar dari semua yang hendak saya lakukan ini adalah statistik. Ada 2 hal yang akan dihitung:<br />
<ol><li>Diberikan halte awal, jam saat itu, dan halte tujuan. Tentukan rata-rata waktu menunggu di halte awal sampai mendapat bus. Sebut saja nilai ini "waktu tunggu".</li>
<li>Diberikan halte awal, jam saat itu, dan halte tujuan yang bertetanggaan dengan halte awal. Tentukan rata-rata waktu tempuh bus untuk berpindah dari halte awal ke halte tujuan. Sebut saja nilai ini "waktu tempuh".</li>
</ol><div>Sebenarnya nilai yang ingin ditemukan bukan hanya rata-rata, melainkan juga median, persentil 90 (p90), persentil 95 (p95), standar deviasi, dan sebagainya. Bahkan, kata orang bijak median mungkin lebih tepat digunakan karena lebih kebal terhadap pencilan (outlier).</div><div><br />
</div><hr /><div><h3>Perhitungan Waktu Tunggu</h3></div><div>Diperlukan permodelan distribusi data yang cocok untuk merepresentasikan "waktu kontinu sampai suatu kejadian terjadi".</div><div><br />
</div><div>Parameter yang diperlukan adalah (tentu saja) halte awal. Parameter lain yang adalah jam, sebab waktu tunggu jam 06.00 pastinya berbeda dengan waktu tunggu jam 17.15. Granularitas jam nantinya dapat diatur sesuka hati, apakah kita ingin mengelompokkan data per 15 menit, 10 menit, atau durasi lainnya. Kemudian parameter terakhir adalah halte tujuan. Meskipun biasanya halte Transjakarta hanya dilalui dua arah, sebenarnya kalau dipandang dari sisi teknis, bisa saja lebih dari dua. Misalnya bus yang lewat di halte S. Parman bisa saja:</div><div><ul><li>sedang mengarah ke Utara, mungkin hanya sampai Grogol atau terus sampai Pluit. </li>
<li>sedang mengarah ke Tenggara, mungkin hanya sampai PGC atau akan sampai di ujung, yaitu Pinang Ranti.</li>
</ul><div>Dari sana saja kita tahu bahwa bus yang lewat di halte S. Parman bisa saja berhenti di 4 kemungkinan halte, tergantung koridor bus tersebut. Tentu saja bus yang hanya sampai PGC pasti lebih banyak dari bus yang sampai Pinang Ranti. Berikut gambaran kasar rutenya:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4V5uEIfZuFteA9zZea_XF9HpEEEiS_nrf7XIqjTR1C2SOF5J4Ny7GVgkzu4dE7p7Z2eJ6LLejoa1m-DIDjp1n3zX8_mXDVvQjxF8-FKX8QiX0wTYY79nDps1MVUl9XnDdU9fXJ2DYWlCL/s1600/g7413.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="297" data-original-width="500" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4V5uEIfZuFteA9zZea_XF9HpEEEiS_nrf7XIqjTR1C2SOF5J4Ny7GVgkzu4dE7p7Z2eJ6LLejoa1m-DIDjp1n3zX8_mXDVvQjxF8-FKX8QiX0wTYY79nDps1MVUl9XnDdU9fXJ2DYWlCL/s320/g7413.png" width="320" /></a></div><br />
</div></div><div>Anggaplah kini kita memiliki data 15 hari sebelumnya untuk seluruh bus yang sampai di halte S. Parman, sekitar pukul 06.00, dan sedang menuju ke Pinang Ranti. Bayangkan data ini digambarkan dalam diagram berikut, dengan h-x menyatakan "x hari sebelumnya", dan titik menyatakan ada bus sampai di halte tersebut pada hari dan jam yang bersangkutan:</div><div><br />
</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHMVAlrzWAgqCLiIBZ7noi9D6FQaSjbNYzIFlcIeA2VZfGSxU55KMPWZF6s1vgW4M09CKlDYy6Mim2jDYRfAJCw2wcAvAEWYlO7Go5ACY3s61W_05gujj0eyxA0ZgiynB-Ulg-Q_YUy5L5/s1600/g7030.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="477" data-original-width="500" height="305" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHMVAlrzWAgqCLiIBZ7noi9D6FQaSjbNYzIFlcIeA2VZfGSxU55KMPWZF6s1vgW4M09CKlDYy6Mim2jDYRfAJCw2wcAvAEWYlO7Go5ACY3s61W_05gujj0eyxA0ZgiynB-Ulg-Q_YUy5L5/s320/g7030.png" width="320" /></a></div><br />
</div><div><br />
</div><div>Misalnya saya hendak ke Pinang Ranti, dan saya sedang berada di halte S. Parman pada saat 06.03. Melihat data 15 hari sebelumnya, saya bisa mengira-ngira berapa lama saya perlu menunggu dari data yang dimiliki. Caranya cukup menghitung rata-rata dari panjang batangan merah berikut:</div><div><br />
</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6H2KOFjoIJThbtEMYgwk7BW1oF1nnbBjyj31B180-Y7Er0_wPSRAKXpNgQjPDvetVN23plUYAolFLPhTAhVpFeUKsvkO1mcQZtN29fMtqQKSbe-QEvtMzP6ULsDDIzo8SY2owvpzB45fr/s1600/g7436.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="477" data-original-width="500" height="305" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6H2KOFjoIJThbtEMYgwk7BW1oF1nnbBjyj31B180-Y7Er0_wPSRAKXpNgQjPDvetVN23plUYAolFLPhTAhVpFeUKsvkO1mcQZtN29fMtqQKSbe-QEvtMzP6ULsDDIzo8SY2owvpzB45fr/s320/g7436.png" width="320" /></a></div><br />
</div><div><br />
</div><div>Ketika seseorang lain tiba di halte pada 06.11. Ia bisa mengira-ngira dengan cara yang sama:</div></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggoQ1vlcqZ3KdyAKG4EL7oRghrg2u1WN75qwDGeVCnNPOT7srndy3NpOp2Edvx5Tiv_oN5bDEIDj58DSXRvZcS58DjxMhtCzGsTJmHTzwcXvn7LBJ0i-5o_Ip-sF-oIykOJy6pPGnJDLXv/s1600/g7297.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="477" data-original-width="500" height="305" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggoQ1vlcqZ3KdyAKG4EL7oRghrg2u1WN75qwDGeVCnNPOT7srndy3NpOp2Edvx5Tiv_oN5bDEIDj58DSXRvZcS58DjxMhtCzGsTJmHTzwcXvn7LBJ0i-5o_Ip-sF-oIykOJy6pPGnJDLXv/s320/g7297.png" width="320" /></a></div><br />
<br />
Tidak hanya rata-rata. Kita juga bisa tahu p90, yang artinya "90% peluang bahwa saya menunggu paling lama x menit".<br />
<br />
Apabila diteliti secara lebih lanjut, "waktu kontinu sampai suatu kejadian terjadi" akan memenuhi distribusi data eksponensial.<br />
<br />
<hr /><h3>Perhitungan Waktu Tempuh</h3>Mirip dengan cara sebelumnya, kita dapat mengumpulkan banyaknya titik data yang merepresentasikan waktu tempuh bus dari suatu halte ke halte tetangganya pada jam sekian.<br />
<br />
Misalnya dalam 15 hari, waktu tempuh dari halte S. Parman ke Grogol untuk jam 05.45 sampai 06.15 adalah sebagai berikut:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNy003grDLVFvyKPQYljbMz4gyRIXMa2ekJQNDLOBowowYCbENHRs6opns2EThjtt32UF885YOU7WLM8pbp3vZny1q-cLV0SSrdxcZnTRuReo_TdAvXy3aIybG7K36xCiKF_qYQ7y0xUCa/s1600/g7397.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="337" data-original-width="500" height="215" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNy003grDLVFvyKPQYljbMz4gyRIXMa2ekJQNDLOBowowYCbENHRs6opns2EThjtt32UF885YOU7WLM8pbp3vZny1q-cLV0SSrdxcZnTRuReo_TdAvXy3aIybG7K36xCiKF_qYQ7y0xUCa/s320/g7397.png" width="320" /></a></div><br />
Waktu tempuh rata-ratanya sesederhana rata-rata nilai-nilai tersebut. Boleh juga dihitung median, p90, atau p95 dari sana.<br />
<br />
<hr /><h3>Nilai-Nilai Turunan</h3><div>Dari kedua jenis informasi di atas, kita dapat menurunkan sejumlah informasi lainnya:</div><div><ul><li>Waktu perkiraan untuk perjalanan yang bermula di halte A, pada pukul T, menuju ke halte tujuan B. Kita dapat menggunakan kombinasi rata-rata waktu tunggu dan waktu tempuh.</li>
<li>Rata-rata dari rata-rata waktu tunggu seluruh halte di suatu koridor, pada suatu periode waktu. Ketika nilainya besar, artinya koridor itu menderita waktu tunggu yang lama pada periode tersebut. Pengelola TJ mungkin perlu memperhatikan pengembangan pada koridor seperti ini.</li>
<li>Rata-rata, dari rata-rata, dari rata-rata waktu tunggu seluruh halte di suatu koridor selama sehari. Nilai ini bisa dimengerti sebagai "indeks kepuasan koridor". Koridor dengan waktu tunggu cepat sepanjang hari akan memiliki nilai yang rendah.</li>
<li>Ruas jalan yang luar biasa macet, dengan mengurutkan seluruh waktu tempuh antar halte bertetanggaan setiap segmen waktu. Ruas jalan seperti ini memberikan efek leher botol (bottleneck) yang sama saja membuat seluruh TJ di koridor tersebut berjalan lambat. Tindak lanjut mengurangi leher botol itu dipastikan meningkatkan kepuasan penggunaan TJ.</li>
</ul><div>Seluruh data tersebut juga dapat dibandingkan dari waktu ke waktu. Misalnya pada tahun 2016, rata-rata waktu tunggu meningkat 14% dari tahun sebelumnya. Ada apa gerangan? Apakah banyak bus yang sudah rusak tetapi tidak diganti? Semua informasi ini bisa membantu Jakarta untuk "men-debug" masalah transportasi dengan TJ.</div><div><br />
</div><div>Sepertinya saya berpikir terlalu jauh. Namun rasanya semua itu masuk akal, dan saya segera mencatatnya. Cukup mengejutkan juga bahwa lamanya waktu pulang dari kantor ke rumah naik TJ cukup bagi saya untuk memikirkan semua itu.<br />
<br />
</div></div><hr /><div><h3>Menamakan Proyek</h3><div>Saya biasa menamakan proyek berdasarkan makanan terakhir yang saya makan. Berhubung waktu itu saya makan sambel terasi dari catering kantor, saya sebut saja proyek ini "Terasi". Kebetulan Terasi memiliki edit distance yang rada kecil dengan "transit".</div><div><br />
</div><hr /><h3>Langkah Selanjutnya</h3></div><div>Berikutnya saya perlu mencari tahu apakah Jakarta memiliki data seperti ini. Saya pernah ikut Hackathon Jakarta 2015, dan mereka memiliki API Tranjakarta. Ditambah lagi baru-baru itu sedang heboh "Jakarta Smart City", yang menggembor-gemborkan data Jakarta dibuka secara umum dalam bentuk API. Mungkin saya bisa periksa kembali API tersebut dan memanen data dari sana.</div><div><br />
</div>Sekian dulu untuk cerita pada kesempatan ini. Cerita selanjutnya akan membahas tentang eksekusi pemanenan data.<br />
<br />
</div>William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-38109114286289368352019-02-02T14:58:00.000+07:002019-02-04T05:33:30.016+07:00Struktur Data Dynamic Range Query<div style="display: none;">
</div>
Tanpa terasa saya sudah membahas banyak struktur data dynamic range query. Tulisan ini akan menjadi rangkuman semuanya dan menjadi peta bagi kalian yang hendak mempelajarinya.<br />
<br />
<hr />
<h3>
Technology Tree</h3>
Intermezzo:<br />
<blockquote class="tr_bq">
<i>Kalau kalian pernah bermain game berjenis Real Time Strategy (RTS), mungkin tahu tentang konsep "technology tree". Biasanya Anda akan memainkan satu peradaban yang dimulai dengan teknologi seadanya. Seiring dengan berjalannya waktu, Anda dapat melakukan riset untuk memajukan teknologi dan meningkatkan produktivitas. Ditemukannya suatu teknologi juga memungkinkan pembuatan sejumlah teknologi lain. Contohnya pada game Age of Empires, pembuatan kandang hewan akan memungkinkan dibuatnya cavalry dan pemanah berkuda.</i></blockquote>
Oke cukup dengan cerita game. Pembelajaran struktur data pun dapat digambarkan seperti sebuah technology tree. Mempelajari suatu struktur data memungkinkan Anda untuk belajar sejumlah struktur data lainnya. Berikut adalah gambarannya:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMlYVDxtx9zsipTvsg2kxlX3QiCNe-RFvYJ6x00REbAJikl2FVrHYAI7xwcoDy5NvqT2OkN_4PMfN6Yy1AmKnvf-nmGrZLuq0X0QobH9l848hZzamSryPHhlCL9XxwBkjCv7jGDTOyLnqD/s1600/rect4290.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="339" data-original-width="850" height="219" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMlYVDxtx9zsipTvsg2kxlX3QiCNe-RFvYJ6x00REbAJikl2FVrHYAI7xwcoDy5NvqT2OkN_4PMfN6Yy1AmKnvf-nmGrZLuq0X0QobH9l848hZzamSryPHhlCL9XxwBkjCv7jGDTOyLnqD/s1600/rect4290.png" width="550" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Klik untuk memperbesar gambar.</td></tr>
</tbody></table>
<br />
Semuanya dimulai dengan array. Dengan array polos, kita dapat melakukan apa saja walaupun secara kurang efektif. Lalu saya tidak mengikutkan keluarga struktur data linear seperti stack/queue/deque, dan juga keluarga heap.<br />
<br />
Anak panah menunjukkan bahwa setelah mempelajari suatu konsep (struktur data/teknik), Anda dapat beranjak ke konsep yang lebih lanjut.<br />
<br />
<a name='more'></a><br />
Berikut penjelasan singkat struktur data sesuai nomor pada gambar:<br />
<br />
<b>1. Sparse Table</b><br />
Digunakan untuk mencari menjawab pencarian minimum/maksimum dalam suatu rentang. Idenya adalah membuat tabel DP yang menyimpan nilai minimum/maksimum untuk k elemen yang bersebelahan, dengan k=1, 2, 4, 8, 16, ....<br />
<br />
Kompleksitas pembuatan struktur datanya O(N log N), dan kompleksitas menjawab pertanyaannya O(1). Meskipun query-nya sangat cepat, sparse table tidak mendukung operasi update dan kegunaannya cukup terbatas,<br />
<br />
Saya mempelajari struktur data ini dari Felik dan <a href="https://www.topcoder.com/community/competitive-programming/tutorials/range-minimum-query-and-lowest-common-ancestor/#Sparse_Table_(ST)_algorithm">Topcoder</a>.<br />
Variasi dari sparse table:<br />
<ol>
<li>Untuk menjawab pertanyaan dengan <a href="https://kupaskode.blogspot.com/2014/03/fixed-size-rmq.html">ukuran rentang yang sama</a>.</li>
<li>Untuk mendapatkan <a href="https://kupaskode.blogspot.com/2017/05/struktur-data-sparse-table.html">nilai elemen ke-k dalam O(log N)</a></li>
</ol>
<br />
<b>2. SQRT Decomposition (Segment Array)</b><br />
Serba bisa untuk menjawab macam-macam pertanyaan. Idenya adalah memecah array N elemen menjadi potongan-potongan array berukuran sqrt(N). Kemudian untuk setiap potongan, dicari nilai agregat dari seluruh elemennya. Struktur data ini relatif mudah untuk diimplementasikan dan konsepnya sangat sederhana.<br />
<br />
Kompleksitas pembuatannya O(N) dan kompleksitas menjawab pertanyaannya O(sqrt(N)).<br />
Saya belajar ini dari <a href="http://www.suhendry.net/blog/?p=1461">blog Pak Suhendry tentang segment array</a>.<br />
<br />
<br />
<b>3. Segment Tree</b><br />
Struktur data serba bisa yang menjadi dasar ilmu beberapa struktur data lainnya. Rasanya ini yang paling penting untuk dikuasai. Kompleksitas pembuatannya O(N), menjawab pertanyaannya O(log N), dan update elemennya O(log N).<br />
<br />
Pelajari segment tree di tulisan saya tentang <a href="https://kupaskode.blogspot.com/2019/01/petunjuk-coding-segment-tree.html">petunjuk coding segment tree</a>.<br />
Dapat di-upgrade untuk mendukung operasi lazy, menjadi persistent, atau menjadi range tree.<br />
<br />
<b><br />
</b> <b>4. Binary Indexed Tree (BIT)</b><br />
Struktur data yang biasanya digunakan untuk mendukung:<br />
<ul>
<li>Range query dari elemen pertama sampai suatu elemen ke-x dalam O(log N)</li>
<li>Update suatu elemen dalam O(log N)</li>
</ul>
BIT memiliki kelebihan dalam kemudahan penulisannya. Biasanya tidak perlu lebih dari 10 baris untuk operasi update dan query. Selain itu, operasi-operasinya sangat ringan sehingga konstanta running-time-nya rendah. Kekurangan BIT adalah tidak semua operasi dapat dengan mudah didukung.<br />
<br />
Kecepatan coding dan running-time-nya membuat struktur data ini penting dalam kompetisi sejenis ICPC.<br />
<br />
Saya belajar BIT pada Pelatnas 2 dari Ashar dan Aji.<br />
Ilmunya saya tuliskan di <a href="https://kupaskode.blogspot.com/2017/07/struktur-data-binary-indexed-tree-bit.html">tulisan tentang BIT</a>.<br />
<b><br />
</b> <br />
<b>5. Binary Search Tree (BST)</b><br />
Mendukung operasi insert, delete, dan find dalam O(log N).<br />
Berbeda dengan update, operasi insert/delete bersifat menyisipkan/membuang elemen. Jadi indeks elemen dapat bergeser. Konsep BST diimplementasikan pada STL C++ yang bernama "set" dan "map".<br />
<br />
Dalam pemrograman kompetitif, implementasi BST saja tidak cukup. Biasanya ada kasus yang membuat BST menjadi timpang, sehingga kompleksitas operasinya mendekati O(N). Diperlukan algoritma yang dapat menyeimbangkan BST, menjadi BBST (Balanced Binary Search Tree). Keberadaan algoritma itu menghasilkan varian BBST seperti AVL Tree, Red Black Tree, Treap, atau Splay Tree.<br />
<br />
Bila Anda ingin tahu tentang BST yang polos, kunjungi <a href="https://en.wikipedia.org/wiki/Binary_search_tree">Wikipedia</a>.<br />
Dengan sedikit modifikasi, <a href="https://kupaskode.blogspot.com/2017/06/bst-sebagai-segment-tree.html">BST dapat digunakan seperti segment tree</a>.<br />
<br />
<b><br />
</b> <b>6. Lowest Common Ancestor (LCA)</b><br />
Ketika data yang diberikan berupa tree, kita dapat mencari leluhur terakhir dari sepasang node dalam O(log N). Implementasinya dapat menggunakan sparse table. Kalau mau repot sedikit, reduksi RMQ dapat membuat kompleksitas pencariannya O(1).<br />
<br />
Pengetahuan tentang LCA diperlukan baik untuk tingkat IOI maupun ICPC. Saya belajar LCA lewat <a href="https://www.topcoder.com/community/competitive-programming/tutorials/range-minimum-query-and-lowest-common-ancestor/#Lowest%20Common%20Ancestor%20(LCA)">Topcoder</a>.<br />
<br />
<br />
<b>7. MO's Algorithm</b><br />
Konsep dekomposisi sqrt(N) dapat diterapkan pada query-nya. Kompleksitas akhir per query menjadi <a href="https://kupaskode.blogspot.com/2017/05/amortized-analysis.html">amortized</a> O(sqrt(N)). Algoritma ini dapat digunakan ketika nilai agregat yang perlu disimpan dalam segment tree terlalu besar (misal: tabel frekuensi kemunculan seluruh elemen).<br />
<br />
Saya baru tahu algoritma ini setelah lulus dari TOKI. Penjelasannya bisa ditemukan di <a href="https://rwhendry.blogspot.com/2017/06/MoUpdate.html">blog Reynaldo</a>.<br />
<b><br />
</b> <b><br />
</b> <b>8. Lazy Segment Tree</b><br />
Upgrade dari segment tree yang memungkinkan node-nodenya hanya dibuat saat diperlukan.<br />
Efektif digunakan ketika ukuran arraynya sangat besar (mencapai milyaran) tetapi hanya beberapa elemen saja yang akhirnya disentuh. Kalau pertanyaan-pertanyaannya dapat dikumpulkan semua lalu dijawab pada akhir pemrosesan, sebenarnya kita dapat menggunakan grid compression.<br />
<br />
Saya bahas tentang lazy segment tree lewat <a href="http://kupaskode.blogspot.com/2018/06/lazy-segment-tree.html">tulisan berikut</a>.<br />
<br />
<b><br />
</b> <b>9. Lazy Propagation</b><br />
Upgrade dari segment tree untuk melayani range update dalam O(log N).<br />
Konsepnya adalah menunda update dan menyimpan informasi penundaan itu pada node. Ketika anak-anak node itu diperlukan, barulah operasi update diturunkan ke anak-anaknya.<br />
<br />
Saya membahasnya secara lengkap di tulisan tentang <a href="http://kupaskode.blogspot.com/2019/01/petunjuk-coding-segment-tree-lazy-propagation.html">segment tree lazy propagation</a>.<br />
<br />
<br />
<b>10. Range Tree</b><br />
Upgrade dari segment tree untuk melayani operasi untuk titik-titik data pada ruang d-dimensi. Setiap operasi dilayani dalam O(log<sup>d</sup> N).<br />
<br />
Saya ada membahas tentang <a href="http://kupaskode.blogspot.com/2017/08/range-tree.html">range tree lewat tulisan ini</a>.<br />
<br />
<br />
<b>11. Persistent Segment Tree</b><br />
Lagi-lagi upgrade dari segment tree untuk mengingat seluruh kedudukan data setiap sebelum update. Biasanya digunakan untuk melayani pertanyaan "sebelum update yang ke-k, apakah nilai dari elemen ke-a sampai elemen ke-b?".<br />
<br />
Konsep persistent segment tree sebenarnya cukup sederhana, yaitu membuat segment tree baru setiap sesudah operasi update. Namun, sebagian besar node-nya menggunakan node-node pada versi sebelumnya. Setiap ada operasi update, perlu ditambahkan O(log N) node. Kompleksitas operasi update/query tetaplah O(log N). Saya membahasnya <a href="http://kupaskode.blogspot.com/2014/12/persistent-data-structure.html">di sini</a>.<br />
<br />
<br />
<b>12. Range Update BIT</b><br />
Walaupun idenya agak rumit, BIT dapat digunakan untuk range update.<br />
Kelebihan dan kekurangannya masih mengikuti BIT secara umum.<br />
Pembahasannya ada <a href="https://kupaskode.blogspot.com/2017/08/range-update-bit.html">di sini</a>.<br />
<br />
<br />
<b>13. AVL Tree</b><br />
<b>14. Red-Black Tree</b><br />
AVL dan Red-Black adalah BBST yang populer untuk digunakan dalam pemrograman kompetitif.<br />
Konsepnya dengan rotasi struktur ketika ketimpangan dideteksi.<br />
Kini operasi insert, delete, dan find dijamin memiliki kompleksitas O(log N).<br />
Saya tidak pernah membahasnya di blog ini, karena kekurangan kedua varian BBST ini memiliki kode yang relatif panjang.<br />
<br />
<br />
<b>15. Treap</b><br />
Varian BBST yang lebih saya sukai karena implementasinya relatif pendek.<br />
Konsepnya dengan prioritas yang dibuat secara acak, sehingga struktur datanya bersifat randomized. Prioritas ini membuat kompleksitas insert, delete, dan find memiliki kompleksitas <i>expected</i> O(log N).<br />
Pembahasannya pernah saya tulis <a href="https://kupaskode.blogspot.com/2013/12/treap-alternatif-balanced-binary-search.html">di sini</a>.<br />
<br />
<br />
<b>16. Splay Tree</b><br />
Varian BBST yang menurut saya pendekatannya cukup radikal.<br />
Terdapat sebuah operasi tambahan bernama "splay". Operasi splay untuk nilai x akan membawa node dengan nilai x (atau yang terdekat dengan x bila tidak ada) ke root BST melalui serangkaian rotasi. Untuk melayani insert/delete/find untuk nilai x, awali dulu dengan splay x. Selanjutnya cukup dilihat apa nilai di root BST.<br />
<br />
Dengan perhitungan matematis yang rumit, kompleksitas insert, delete, dan find dibuktikan memiliki kompleksitas <i>amortized</i> O(log N).<br />
<b><br />
</b> Sebenarnya operasi splay yang cukup radikal ini sangat berguna. Membawa sembarang node ke root tanpa merusak struktur urutan BST dapat memudahkan algoritma lainnya. Kemudahan ini dimanfaatkan oleh struktur data link-cut tree.<br />
<br />
Saya belum pernah menulis tentang splay tree di blog ini, tapi pernah untuk perkuliahan.<br />
Penjelasannya dapat Anda baca di <a href="https://en.wikipedia.org/wiki/Splay_tree">Wikipedia</a>.<br />
<br />
<b><br />
</b> <b>17. Heavy-Light Decomposition (HLD)</b><br />
Digunakan ketika dihadapkan pada tree yang masing-masing node-nya menyimpan suatu nilai. Kemudian Anda diminta mencari nilai agregat dari path suatu node ke node lainnya.<br />
<b><br />
</b> Pengetahuan tentang LCA dapat diterapkan pada HLD, yang mendekomposisi suatu tree menjadi serangkaian segmen linear. Masing-masing segmen linear ini dapat dijadikan segment tree. Dijamin bahwa untuk setiap pasang node, pathnya hanya terdiri dari O(log N) segmen saja. Jika kita menerapkan operasi segment tree pada masing-masing segmen, berarti kompleksitas akhir sebuah operasinya O(log<sup>2</sup> N).<br />
<br />
Pelajari konsepnya <a href="https://www.geeksforgeeks.org/heavy-light-decomposition-set-1-introduction/"> di sini</a>.<br />
<br />
<br />
<b>18. Composite Tree</b><br />
Sebenarnya composite tree mengacu pada tree yang didalamnya diisi tree lainnya. Contohnya BIT yang diisi dengan Treap. Setelah menguasai tentang range tree, Anda dapat meng-upgrade-nya menjadi lebih umum. Treenya kini dapat dibuat dan diisi dengan apa saja.<br />
<br />
Baca tulisannya tentang komposisi tree <a href="https://kupaskode.blogspot.com/2013/12/komposisi-struktur-data-dynamic-range.html">di sini</a>.<br />
<br />
<br />
<b>19. Fractional Cascading</b><br />
Merupakan teknik lanjutan untuk mengoptimasi range tree. Kompleksitas operasi untuk data 2 dimensi dapat menjadi O(log N) saja. Penjelasannya dapat dibaca di <a href="http://blog.ezyang.com/2012/03/you-could-have-invented-fractional-cascading/">blog orang ini</a>.<br />
<br />
<br />
<b>20. Link-Cut Tree</b><br />
Sepertinya struktur data paling kompleks yang pernah saya jumpai.<br />
Diberikan sebuah kumpulan tree (forest). Struktur data ini mendukung operasi:<br />
<ul>
<li>link(u, v): hubungkan node u dan v dengan edge (diketahui u dan v belum terhubung)</li>
<li>cut(u, v): buang edge u dan v</li>
<li>find_root(u): cari root node dari subtree yang mengandung u</li>
<li>aggregate(u, v): cari nilai agregat yang disimpan oleh path dari node u sampai node v</li>
</ul>
Baca penjelasannya <a href="http://courses.csail.mit.edu/6.851/spring12/scribe/L19.pdf">di sini</a>.<br />
<br />
<br />
<hr />
<h3>
Latihan</h3>
Berhubung banyak orang yang tertarik dengan beragam struktur data tersebut, banyak forum-forum yang mengumpulkan daftar soalnya. Berikut apa yang saya temukan dan dapat Anda gunakan untuk berburu soal latihan:<br />
<ul>
<li><a href="https://codeforces.com/blog/entry/22616">https://codeforces.com/blog/entry/22616</a></li>
<li><a href="https://a2oj.com/contest?ID=5254">https://a2oj.com/contest?ID=5254</a></li>
<li><a href="http://praveendhinwacoding.blogspot.com/2013/06/700-problems-to-understand-you-complete.html">http://praveendhinwacoding.blogspot.com/2013/06/700-problems-to-understand-you-complete.html</a></li>
</ul>
<br class="Apple-interchange-newline" />
<hr />
<hr />
<h3>
Penutup</h3>
<div>
Disarankan untuk menyimpan catatan berupa implementasi struktur data yang pernah Anda buat. Sepengalaman saya, mudah sekali untuk lupa bagaimana cara coding struktur data tertentu. Lagipula, biasanya hanya terdapat sedikit perbedaan pada bagian agregasi data saja. Catatan ini dapat Anda bawa jika Anda ikut kompetisi seperti ICPC.<br />
<br />
Semoga tulisan ini membantu Anda untuk tahu arah berlatih struktur data. Semoga sukses!<br />
<br /></div>
William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-59021163551142960032019-01-07T12:53:00.000+07:002020-04-06T06:12:47.459+07:00Petunjuk Coding: Segment Tree Lazy Propagation<div style="display: none;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgp6DLul5zyydz3_vJOBY8GhwCDRxQ_9_f3H7yLdJkrMapIuJVf7oiur-YojrOK651convj-DBOT3j0IlQc97pvEOLkE-27ms2wkEMCun3GfgKMB2RuUDWKGh-IZpIKDhFjuXesNevITOXR/s1600/rect4290.png" imageanchor="1"><img border="0" data-original-height="200" data-original-width="200" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgp6DLul5zyydz3_vJOBY8GhwCDRxQ_9_f3H7yLdJkrMapIuJVf7oiur-YojrOK651convj-DBOT3j0IlQc97pvEOLkE-27ms2wkEMCun3GfgKMB2RuUDWKGh-IZpIKDhFjuXesNevITOXR/s320/rect4290.png" width="320" /></a></div>Setelah menguasai <a href="https://kupaskode.blogspot.com/2019/01/petunjuk-coding-segment-tree.html">penulisan kode segment tree</a> yang melayani update sebuah elemen dan range query, kini waktunya beranjak ke operasi yang lebih sulit: range update.<br />
<br />
Perkenalan tentang penanganan range update dengan lazy propagation pernah saya bahas di <a href="https://kupaskode.blogspot.com/2013/07/tentang-segment-tree-lazy-propagation.html">tulisan yang lalu</a>. Pada tulisan ini, kita akan fokus ke implementasinya.<br />
<br />
<hr /><h3>Konsep</h3>Lazy propagation dapat diimplementasikan ketika:<br />
<ol><li>Apabila seluruh elemen yang dicakupi sebuah segmen mendapatkan update, maka update harus disangkutkan pada segmen tersebut dengan efisien & tanpa menyentuh anak-anaknya.</li>
<li>Apabila seluruh elemen yang dicakupi suatu segmen diperlukan dalam query, maka informasi asli dari segmen itu harus bisa didapatkan dengan efisien & tanpa menyentuh anak-anaknya.</li>
</ol>Apabila kedua syarat itu dipenuhi, maka lazy propagation dapat diterapkan. Setiap range update atau query dapat dilaksanakan dalam O(log N).<br />
<br />
Untuk keperluan penjelasan, nilai yang disangkutkan akan saya sebut "nilai lazy".<br />
<br />
Penjelasan tentang cara implementasi akan dijelaskan melalui studi kasus 1 pada bagian selanjutnya. Kita juga akan mengambil abstraksi kode lazy propagation dari sana, untuk diterapkan pada studi kasus seterusnya.<br />
<a name='more'></a><br />
<hr /><h3>Studi Kasus 1: HORRIBLE (SPOJ)</h3>Rasanya soal ini sangat sering dibahas...<br />
Soal lengkap dapat dilihat di <a href="https://www.spoj.com/problems/HORRIBLE">https://www.spoj.com/problems/HORRIBLE</a>.<br />
<br />
Mari kita implementasikan fungsi update, yaitu menambahkan suatu nilai kepada suatu rentang.<br />
Selain menyimpan nilai jumlahan pada setiap segmen, kita perlu menyimpan informasi nilai yang disangkutkan. Nilai ini secara intuitif terpikir adalah nilai yang seharusnya ditambahkan pada masing-masing elemen pada segmen tersebut. Mari kita sebut nilai itu "delayedAdd".<br />
<br />
Kini segment tree kita perlu menyimpan dua nilai, yaitu jumlahan setiap segmen dan delayedAdd. Implementasinya dapat memanfaatkan struct:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
typedef long long LL;
const int MAXN = 132000;
struct data {
LL sum;
LL delayedAdd;
};
data sTree[2*MAXN];
]]></script><!-----></div><br />
Catatan: jangan lupa untuk menginisialisasi nilai sum dan delayedAdd dengan nilai 0.<br />
<br />
Setiap kali setiap elemen yang dicakupi suatu segmen perlu mendapatkan tambahan nilai, kita tambahkan nilai lazy "delayedAdd" dengan nilai tersebut.<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
void update(int id, int l, int r, int xa, int xb, LL delta) {
if ((xa <= l) && (r <= xb)) {
sTree[id].delayedAdd += delta;
} else {
...
}
}
]]></script><!-----></div><br />
Nah untuk kasus update yang tidak dilakukan pada seluruh elemen pada segmen, kita perlu turun ke anak-anak segmen itu secara rekursif (seperti range query biasanya). Namun ketika segmen ini memiliki nilai lazy, kita <b>harus melanjutkan pekerjaan yang tertunda itu</b>. Caranya:<br />
<ol><li>Menurunkan nilai lazy dari segmen itu ke anak-anaknya, sehingga segmen itu sudah tidak lagi menyimpan nilai lazy. Bayangkan seperti pekerjaan di segmen tersebut yang tertunda sudah selesai dilaksanakan. Tahap ini sering disebut "propagate".</li>
<li>Lanjut melakukan range update pada anaknya yang bersangkutan.</li>
<li>Saat range update anak-anak selesai dilakukan, perbaharui nilai segmen dengan memanfaatkan informasi dari anak-anaknya (seperti tahap "MERGE" yang diperkenalkan tulisan sebelumnya).</li>
</ol>Untuk "propagate", kita cukup menambahkan nilai lazy anak-anak suatu segmen dengan nilai lazy yang dikandung elemen tersebut. Supaya rapi saya akan mendefinisikan fungsi "PROPAGATE":<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
void PROPAGATE(int id) {
sTree[2*id+1].delayedAdd += sTree[id].delayedAdd;
sTree[2*id+2].delayedAdd += sTree[id].delayedAdd;
sTree[id].delayedAdd = 0;
}
void update(int id, int l, int r, int xa, int xb, LL delta) {
if ((xa <= l) && (r <= xb)) {
sTree[id].delayedAdd += delta;
} else {
int m = (l + r)/2;
PROPAGATE(id);
...
}
}
]]></script><!-----></div>Penting untuk disadari bahwa sesudah nilai lazy diteruskan ke anak-anak, segmen tersebut sudah bersih. Tidak ada lagi nilai lazy, sehingga delayedAdd perlu dijadikan 0.<br />
<br />
Langkah kedua, lakukan update pada anak-anak segmen seperlunya.<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
void PROPAGATE(int id) {
sTree[2*id+1].delayedAdd += sTree[id].delayedAdd;
sTree[2*id+2].delayedAdd += sTree[id].delayedAdd;
sTree[id].delayedAdd = 0;
}
void update(int id, int l, int r, int xa, int xb, LL delta) {
if ((xa <= l) && (r <= xb)) {
sTree[id].delayedAdd += delta;
} else {
int m = (l + r)/2;
PROPAGATE(id);
if (xa <= m) update(2*id+1, l, m, xa, xb, delta);
if (xb > m) update(2*id+2, m+1, r, xa, xb, delta);
...
}
}
]]></script><!-----></div><br />
Terakhir, perbaharui nilai segmen saat ini berdasarkan informasi dari anak-anaknya. Hal ini perlu dilakukan karena segmen tersebut sudah terbebas dari nilai lazy, yang mana telah diteruskan ke anak-anaknya.<br />
<br />
Perhatikan bahwa anak-anaknya mungkin menyimpan nilai lazy. Ketika suatu segmen menyimpan nilai lazy, nilai asli jumlahan yang dikandung segmen itu adalah: sum + delayedAdd*banyaknya_elemen. Supaya rapi, saya mendefinisikan fungsi "GET" untuk mendapatkan nilai asli suatu segmen. Berhubung banyaknya elemen dalam segmen diperlukan, saya menjadikan batasan kiri dan kanan segmen sebagai parameter dalam "GET":<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
void PROPAGATE(int id) {
sTree[2*id+1].delayedAdd += sTree[id].delayedAdd;
sTree[2*id+2].delayedAdd += sTree[id].delayedAdd;
sTree[id].delayedAdd = 0;
}
LL GET(int id, int l, int r) {
return sTree[id].sum + sTree[id].delayedAdd*(r-l+1);
}
void update(int id, int l, int r, int xa, int xb, LL delta) {
if ((xa <= l) && (r <= xb)) {
sTree[id].delayedAdd += delta;
} else {
int m = (l + r)/2;
PROPAGATE(id);
if (xa <= m) update(2*id+1, l, m, xa, xb, delta);
if (xb > m) update(2*id+2, m+1, r, xa, xb, delta);
sTree[id].sum = GET(2*id+1, l, m) + GET(2*id+2, m+1, r);
}
}
]]></script><!-----></div><br />
Sadari bahwa proses menempatkan nilai lazy pada base case sebenarnya memiliki konsep yang sama dengan menempatkan nilai lazy kepada anak-anak dalam fungsi "PROPAGATE". Supaya lebih rapi, kita bisa me-refactor-nya menjadi sebuah fungsi "LAZY_UPDATE":<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
void LAZY_UPDATE(int id, LL delta) {
sTree[id].delayedAdd += delta;
}
void PROPAGATE(int id) {
LAZY_UPDATE(2*id+1, sTree[id].delayedAdd);
LAZY_UPDATE(2*id+2, sTree[id].delayedAdd);
sTree[id].delayedAdd = 0;
}
LL GET(int id, int l, int r) {
return sTree[id].sum + sTree[id].delayedAdd*(r-l+1);
}
void update(int id, int l, int r, int xa, int xb, LL delta) {
if ((xa <= l) && (r <= xb)) {
LAZY_UPDATE(id, delta);
} else {
int m = (l + r)/2;
PROPAGATE(id);
if (xa <= m) update(2*id+1, l, m, xa, xb, delta);
if (xb > m) update(2*id+2, m+1, r, xa, xb, delta);
sTree[id].sum = GET(2*id+1, l, m) + GET(2*id+2, m+1, r);
}
}
]]></script><!-----></div><br />
Dan selesailah fungsi update. Lanjut ke fungsi query yang konsepnya mirip.<br />
Mulai dengan membuat kasus ketika segmen sepenuhnya dicakupi rentang yang ditanyakan. Pada kasus ini, cukup kembalikan nilai sum + delayedAdd*banyaknya_elemen. Hal ini sama dengan fungsi "GET" yang telah kita buat, jadi cukup digunakan kembali:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
LL query(int id, int l, int r, int xa, int xb) {
if ((xa <= l) && (r <= xb)) {
return GET(id,l,r);
}else{
...
}
}
]]></script><!-----></div><br />
Untuk kasus anaknya perlu diperiksa, kita akan mengulang ketiga proses seperti pada update. Pertama, lakukan propagate nilai lazy ke anak-anaknya:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
LL query(int id, int l, int r, int xa, int xb) {
if ((xa <= l) && (r <= xb)) {
return GET(id,l,r);
}else{
int m = (l + r)/2;
PROPAGATE(id);
...
}
}
]]></script><!-----></div><br />
Kedua, query anak-anaknya yang mengandung rentang yang ditanyakan:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
LL query(int id, int l, int r, int xa, int xb) {
if ((xa <= l) && (r <= xb)) {
return GET(id,l,r);
}else{
int m = (l + r)/2;
PROPAGATE(id);
LL ret = 0;
if (xa <= m) ret += query(2*id+1, l, m, xa, xb);
if (xb > m) ret += query(2*id+2, m+1, r, xa, xb);
...
}
}
]]></script><!-----></div><br />
Terakhir, perbaharui nilai segmen ini menggunakan informasi dari anak-anaknya. Kembali diulang bahwa ini perlu dilakukan karena segmen tersebut sudah terbebas dari nilai lazy, yang mana telah diteruskan ke anak-anaknya. Selesailah fungsi query, dan jangan lupa untuk return nilai di akhir.<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
LL query(int id, int l, int r, int xa, int xb) {
if ((xa <= l) && (r <= xb)) {
return GET(id,l,r);
}else{
int m = (l + r)/2;
PROPAGATE(id);
LL ret = 0;
if (xa <= m) ret += query(2*id+1, l, m, xa, xb);
if (xb > m) ret += query(2*id+2, m+1, r, xa, xb);
sTree[id].sum = GET(2*id+1, l, m) + GET(2*id+2, m+1, r);
return ret;
}
}
]]></script><!-----></div><br />
Berhubung proses memperbaharui nilai orang tua dari anak-anaknya digunakan di update dan query, saya me-refactor-nya ke dalam fungsi "MERGE". Berikut implementasi lengkap untuk HORRIBLE:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
typedef long long LL;
const int MAXN = 132000;
struct data {
LL sum;
LL delayedAdd;
};
data sTree[2*MAXN];
LL GET(int id, int l, int r) {
return sTree[id].sum + sTree[id].delayedAdd*(r-l+1);
}
void LAZY_UPDATE(int id, LL delta) {
sTree[id].delayedAdd += delta;
}
void PROPAGATE(int id) {
LAZY_UPDATE(2*id+1, sTree[id].delayedAdd);
LAZY_UPDATE(2*id+2, sTree[id].delayedAdd);
sTree[id].delayedAdd = 0;
}
void MERGE(int id, int l, int m, int r) {
sTree[id].sum = GET(2*id+1, l, m) + GET(2*id+2, m+1, r);
}
void update(int id, int l, int r, int xa, int xb, LL delta) {
if ((xa <= l) && (r <= xb)) {
LAZY_UPDATE(id, delta);
} else {
int m = (l + r)/2;
PROPAGATE(id);
if (xa <= m) update(2*id+1, l, m, xa, xb, delta);
if (xb > m) update(2*id+2, m+1, r, xa, xb, delta);
MERGE(id, l, m, r);
}
}
LL query(int id, int l, int r, int xa, int xb) {
if ((xa <= l) && (r <= xb)) {
return GET(id,l,r);
}else{
int m = (l + r)/2;
PROPAGATE(id);
LL ret = 0;
if (xa <= m) ret += query(2*id+1, l, m, xa, xb);
if (xb > m) ret += query(2*id+2, m+1, r, xa, xb);
MERGE(id, l, m, r);
return ret;
}
}
int main() {
int nTc;
scanf("%d", &nTc);
for (int jt = 0; jt < nTc; jt++) {
int N, Q;
scanf("%d%d", &N, &Q);
// Reset
for (int i = 0; i < 2*MAXN; i++) {
sTree[i].sum = 0;
sTree[i].delayedAdd = 0;
}
for (int i = 0; i < Q; i++) {
int a, b, delta, jq;
scanf("%d", &jq);
if (jq == 0) {
scanf("%d%d%d", &a, &b, &delta);
update(0, 0, N-1, a-1, b-1, delta);
} else {
scanf("%d%d", &a,&b);
printf("%lld\n", query(0, 0, N-1, a-1, b-1));
}
}
}
return 0;
}
]]></script><!-----></div><br />
Demikianlah implementasi lazy propagation pada soal yang klasik. Polanya sudah terlihat dari implementasi kedua fungsi di atas, yaitu:<br />
<ol><li>Apabila segmen ini dicakup sepenuhnya pada rentang yang diminta, lakukan LAZY_UPDATE (untuk update) atau GET (untuk query).</li>
<li>Apabila segmennya tidak dicakup sepenuhnya pada rentang yang diminta, telusuri anak-anaknya.</li>
<ol><li>Sebelum melanjutkan operasi, lakukan dulu PROPAGATE yang akan mewariskan nilai lazy segmen ke anak-anaknya menggunakan LAZY_UPDATE.</li>
<li>Teruskan operasi ke anak-anaknya.</li>
<li>Baik untuk update maupun query, lakukan MERGE untuk mencari nilai asli (tanpa nilai lazy-lazy-an) dari segmen tersebut menggunakan GET dari anak-anaknya.</li>
</ol></ol>Jadi selama Anda bisa mengimplementasikan fungsi-fungsi berikut secara efisien:<br />
<ol><li>LAZY_UPDATE</li>
<li>GET</li>
<li>PROPAGATE</li>
<li>MERGE</li>
</ol>berarti selesailah lazy propagation Anda. Keempat fungsi tersebutlah fungsi abstrak pada lazy propagation, yang perlu disesuaikan dengan masalah yang dihadapi.<br />
<br />
Mari kita lihat studi kasus berikutnya untuk memperjelas pemahaman tentang implementasi keempat fungsi tersebut.<br />
<br />
<hr /><h3>Studi Kasus 2: Circular RMQ (Codeforces)</h3><div>Soal lengkap dapat dibaca di sini: <a href="https://codeforces.com/contest/52/problem/C">https://codeforces.com/contest/52/problem/C</a></div>Intinya adalah, diberikan array bilangan a dan 2 macam operasi:<br />
<ol><li>inc(l, r, v): tambahkan v pada a[l], a[l+1], a[l+2], ..., a[r].</li>
<li>rmq(l, r): cari nilai terkecil di antara a[l], a[l+1], a[l+2], ..., a[r].</li>
</ol><div>Urusan sirkularnya tidak menjadi masalah, sebab operasi yang "menembus" elemen terakhir dapat dipecah menjadi 2. Contohnya operasi pada elemen ke 8 sampai ke 3 pada array zero-based dengan 10 elemen cukup dilakukan pada a[8..9] dan a[0..3] secara terpisah.<br />
<br />
Nilai yang wajib kita simpan jelas adalah nilai elemen terkecil dari suatu segmen. Lalu apa nilai lazy-nya? Secara intuitif kita dapat menggunakan konsep yang serupa dengan soal sebelumnya, yaitu "delayedAdd".<br />
<br />
Apakah dengan "delayedAdd" kita bisa mendapatkan nilai terkecil dari suatu segmen dengan efisien tanpa melirik anak-anaknya? Jawabannya: ya, karena nilai terkecilnya pastilah nilai terkecil yang tersimpan ditambah "delayedAdd".</div><br />
Saya persilakan Anda untuk memahami implementasi berikut untuk lebih jelasnya. Fungsi update dan query nyaris sama persis dengan fungsi yang sama pada kode HORRIBLE. Yang berbeda signifikan hanya cara mengimplementasikan keempat fungsi abstraknya.<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
typedef long long LL;
const int MAXN = 265000;
const LL INF = 2123123123LL * 2123123123LL;
struct data {
LL minValue;
LL delayedAdd;
};
data sTree[2*MAXN];
char sc[100];
LL GET(int id) {
return sTree[id].minValue + sTree[id].delayedAdd;
}
void LAZY_UPDATE(int id, int delta) {
sTree[id].delayedAdd += delta;
}
void PROPAGATE(int id) {
LAZY_UPDATE(2*id+1, sTree[id].delayedAdd);
LAZY_UPDATE(2*id+2, sTree[id].delayedAdd);
sTree[id].delayedAdd = 0;
}
void MERGE(int id) {
sTree[id].minValue = min(GET(2*id+1), GET(2*id+2));
}
void update(int id, int l, int r, int xa, int xb, int delta) {
if ((xa <= l) && (r <= xb)) {
LAZY_UPDATE(id, delta);
} else {
int m = (l + r)/2;
PROPAGATE(id);
if (xa <= m) update(2*id+1, l, m, xa, xb, delta);
if (xb > m) update(2*id+2, m+1, r, xa, xb, delta);
MERGE(id);
}
}
LL query(int id, int l, int r, int xa, int xb) {
if ((xa <= l) && (r <= xb)) {
return GET(id);
}else{
int m = (l + r)/2;
PROPAGATE(id);
LL ret = INF;
if (xa <= m) ret = min(ret, query(2*id+1, l, m, xa, xb));
if (xb > m) ret = min(ret, query(2*id+2, m+1, r, xa, xb));
MERGE(id);
return ret;
}
}
int main() {
int N, Q;
scanf("%d", &N);
// Inisialisasi
for (int i = 0; i < 2*MAXN; i++) {
sTree[i].minValue = 0;
sTree[i].delayedAdd = 0;
}
// Build cara malas = update 1 per 1 (total: O(N log N))
for (int i = 0; i < N; i++) {
int val;
scanf("%d", &val);
update(0, 0, N-1, i, i, val);
}
scanf("%d", &Q);
gets(sc); // Baca newline sesudah banyaknya query
for (int nq = 0; nq < Q; nq++) {
// Parse input
gets(sc);
int nSpace = 0;
int len = strlen(sc);
for (int i = 0; i < len; i++) {
if (sc[i] == ' ') {
nSpace++;
}
}
int a, b;
LL v;
if (nSpace == 1) {
// Query
sscanf(sc, "%d %d", &a, &b);
LL ans = INF;
if (a <= b) {
ans = query(0, 0, N-1, a, b);
} else {
ans = min(query(0, 0, N-1, 0, b), query(0, 0, N-1, a, N-1));
}
printf("%I64d\n", ans);
} else {
// Update
sscanf(sc, "%d %d %I64d", &a, &b, &v);
if (a <= b) {
update(0, 0, N-1, a, b, v);
} else {
update(0, 0, N-1, 0, b, v);
update(0, 0, N-1, a, N-1, v);
}
}
}
return 0;
}
]]></script><!-----></div><br />
<hr /><h3>Studi Kasus 3: Ahoy Pirates (UVa)</h3><div>Soal lengkap dapat dibaca di: <a href="https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2397">UVa 11402</a>.</div><br />
Kali ini kita diberikan barisan berisi nilai 0 dan 1. Angka 0 menyatakan "Barbary" dan angka 1 menyatakan "Buccaneer".<br />
<br />
Terdapat beberapa jenis operasi yang disebut "sihir" oleh soalnya:<br />
<ul><li>F a b: ubah elemen ke-a sampai elemen ke-b menjadi angka 1</li>
<li>E a b: ubah elemen ke-a sampai elemen ke-b menjadi angka 0</li>
<li>I a b: ubah elemen ke-a sampai elemen ke-b dari 0 menjadi 1, atau dari 1 menjadi 0 (dibalik)</li>
<li>S a b: hitung banyaknya angka 1 dari elemen ke-a sampai elemen ke-b</li>
</ul>Melayani operasi F dan E sangat mudah, kita cukup menyimpan nilai lazy yang menyatakan apakah suatu segmen berada dalam efek F, atau E, atau tidak sama sekali. Sihir F dan E akan selalu menimpa nilai lazy apapun yang disimpan suatu segmen. Jika diketahui bahwa suatu segmen berada dalam pengaruh F atau E, banyaknya angka 1 dapat ditemukan dengan mudah.<br />
<br />
Untuk melayani operasi I, kita bagi per kasusnya:<br />
<ol><li>Ketika segmen berada dalam pengaruh F, kini menjadi E</li>
<li>Ketika segmen berada dalam pengaruh E, kini menjadi F</li>
<li>Jika tidak dalam pengaruh sihir apa-apa, kita tandakan bahwa segmen ini berada dalam efek I.</li>
<li>Jika ternyata segmen ini berada dalam efek I, kini menjadi tidak dalam pengaruh sihir apa-apa.</li>
</ol><div>Setelah memahami bagaimana melayani operasi F, E, dan I, kita selesai mendefinisikan bagaimana fungsi LAZY_UPDATE bekerja.<br />
<br />
Untuk operasi GET, jelas kita perlu menyimpan banyaknya elemen bernilai 1 pada suatu segmen. Mari kita sebut banyaknya elemen bernilai 1 dalam suatu segmen sebagai "nBuc". Saat teradpat nilai lazy, nilai nBuc sesungguhnya dapat dicari dengan cara berikut:</div><div><ol><li>Jika sedang dalam pengaruh F, kembalikan banyaknya elemen yang dicakup segmen tersebut.</li>
<li>Jika sedang dalam pengaruh E, kembalikan 0.</li>
<li>Jika tidak sedang dalam pengaruh sihir apa-apa, kembalikan nBuc.</li>
<li>Jika sedang dalam pengaruh I, kembalikan banyaknya elemen 0, yang sama saja dengan banyaknya elemen segmen tersebut dikurangi nBuc.</li>
</ol><div>Setelah LAZY_UPDATE dan GET didefinisikan, PROPAGATE dan MERGE tinggal mengikuti saja. Lagi-lagi fungsi update dan query nyaris sama persis dengan fungsi yang sama pada kode HORRIBLE. Perbedaan signifikan hanya dalam cara mengimplementasikan keempat fungsi abstraknya. Berikut implementasi lengkapnya:</div></div><div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
const int MAXN = 1050000;
const int BUC = 1;
const int BAR = 0;
const int NONE = -1;
const int ALL_BUC = 0;
const int ALL_BAR = 1;
const int INVERSE = 2;
struct data {
int nBuc;
int state;
};
data sTree[2*MAXN];
int ar[MAXN];
char sc[55];
int GET(int id, int l, int r) {
if (sTree[id].state == NONE) {
return sTree[id].nBuc;
} else if (sTree[id].state == ALL_BUC) {
return r-l+1;
} else if (sTree[id].state == ALL_BAR) {
return 0;
} else {
// Inverse
return r-l+1 - sTree[id].nBuc;
}
}
void LAZY_UPDATE(int id, int l, int r, int state) {
// if state == NONE: no action
if (state == ALL_BUC) {
sTree[id].state = ALL_BUC;
} else if (state == ALL_BAR) {
sTree[id].state = ALL_BAR;
} else if (state == INVERSE) {
if (sTree[id].state == NONE) {
sTree[id].state = INVERSE;
} else if (sTree[id].state == ALL_BUC) {
sTree[id].state = ALL_BAR;
} else if (sTree[id].state == ALL_BAR) {
sTree[id].state = ALL_BUC;
} else if (sTree[id].state == INVERSE) {
sTree[id].state = NONE;
}
}
}
void PROPAGATE(int id, int l, int m, int r) {
LAZY_UPDATE(2*id+1, l, m, sTree[id].state);
LAZY_UPDATE(2*id+2, m+1, r, sTree[id].state);
sTree[id].state = NONE;
}
void MERGE(int id, int l, int m, int r) {
sTree[id].nBuc = GET(2*id+1, l, m) + GET(2*id+2, m+1, r);
}
void build(int id, int l, int r) {
sTree[id].state = NONE;
if (l == r) {
sTree[id].nBuc = (ar[l] == BUC) ? 1 : 0;
} else {
int m = (l + r)/2;
build(2*id+1, l, m);
build(2*id+2, m+1, r);
MERGE(id, l, m, r);
}
}
void update(int id, int l, int r, int xa, int xb, int state) {
if ((xa <= l) && (r <= xb)) {
LAZY_UPDATE(id, l, r, state);
} else {
int m = (l + r)/2;
PROPAGATE(id, l, m, r);
if (xa <= m) update(2*id+1, l, m, xa, xb, state);
if (xb > m) update(2*id+2, m+1, r, xa, xb, state);
MERGE(id, l, m, r);
}
}
int query(int id, int l, int r, int xa, int xb) {
if ((xa <= l) && (r <= xb)) {
return GET(id, l, r);
}else{
int m = (l + r)/2;
PROPAGATE(id, l, m, r);
int ret = 0;
if (xa <= m) ret += query(2*id+1, l, m, xa, xb);
if (xb > m) ret += query(2*id+2, m+1, r, xa, xb);
MERGE(id, l, m, r);
return ret;
}
}
int main() {
int nTc;
scanf("%d", &nTc);
for (int jt = 0; jt < nTc; jt++) {
int N, M, Q;
scanf("%d", &M);
N = 0;
for (int i = 0; i < M; i++) {
int T;
scanf("%d", &T);
scanf("%s", sc);
int len = strlen(sc);
for (int rep = 0; rep < T; rep++) {
for (int j = 0; j < len; j++) {
ar[N++] = sc[j] - '0';
}
}
}
build(0, 0, N-1);
scanf("%d", &Q);
printf("Case %d:\n", jt+1);
int nQuery = 0;
for (int nq = 0; nq < Q; nq++) {
int a, b;
scanf("%s %d %d", sc, &a, &b);
if (sc[0] == 'F') {
update(0, 0, N-1, a, b, ALL_BUC);
} else if (sc[0] == 'E') {
update(0, 0, N-1, a, b, ALL_BAR);
} else if (sc[0] == 'I') {
update(0, 0, N-1, a, b, INVERSE);
} else {
nQuery++;
printf("Q%d: %d\n", nQuery, query(0, 0, N-1, a, b));
}
}
}
return 0;
}
]]></script><!-----></div><br />
<hr /><h3>Latihan</h3><div>Tiada kata "bisa" tanpa latihan. Silakan Anda coba terapkan lazy propagation pada soal-soal berikut:<br />
<a href="https://www.spoj.com/problems/RPAR/">SPOJ RPAR</a></div><div><a href="https://codeforces.com/contest/145/problem/E">Codeforces: lucky Queries</a><br />
<a href="https://training.ia-toki.org/problemsets/136/problems/766/">TLX: DNA Misterius (CompFest 2013)</a><br />
<div><br />
</div><hr /><hr /><h3>Penutup</h3></div><div>Rasanya butuh waktu beberapa bulan sampai saya benar-benar paham cara kerja lazy propagation. Setelah mengulas 3 contoh, saya berharap penjelasan tentang lazy propagation menjadi jelas dan Anda menangkap gambaran kasarnya tanpa perlu menghabiskan berbulan-bulan seperti saya.<br />
<br />
</div>William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-4326793489562444112019-01-02T12:54:00.000+07:002019-01-07T12:55:40.353+07:00Petunjuk Coding: Segment Tree<div style="display: none;">
</div>
Sebenarnya tulisan ini mau difokuskan ke implementasi segment tree. Tapi karena tulisan saya sebelumnya di ristekfasilkom.com sudah musnah (hosting-annya habis), jadinya saya kembalikan back-up tulisannya ke sini.<br />
<br />
Segment tree merupakan struktur data yang biasa dipakai untuk menjawab <i>dynamic range</i><br />
<i>query</i>, yaitu sejumlah operasi yang dilakukan dengan rentang yang bervariasi. Selama tidak ada penyisipan atau penghapusan elemen, segment tree hampir selalu bisa digunakan. Tulisan ini akan membahas tentang konsep struktur data ini dan cara implementasinya.<br />
<br />
<hr />
<h3>
Permasalahan Motivasi</h3>
Diberikan sebuah array integer dan serangkaian operasi.<br />
Sebut saja array itu adalah ar. Operasi-operasi yang diberikan bisa berupa:<br />
<ul>
<li>update(x, v), yang artinya mengubah isi ar[x] menjadi v.</li>
<li>query(x, y), yang artinya mencari nilai minimum dari ar[x], ar[x+1], ..., ar[y].</li>
</ul>
Dengan cara naif, update dapat dilaksanakan dalam O(1). Sementara query dapat dilaksanakan dalam O(N), dengan N adalah panjang array. Tentunya cara ini kurang efisien, khususnya jika N cukup besar dan operasi yang diberikan cukup banyak.<br />
<br />
Dengan segment tree, kita dapat melakukannya dengan lebih baik.<br />
<a name='more'></a><br />
<hr />
<h3>
Ide Dasar</h3>
Ide dasarnya adalah:<br />
<ol>
<li>Pembagian pertanggungjawaban.</li>
<li>Cara mendapatkan informasi untuk segmen yang besar dari dua segmen yang lebih kecil.</li>
</ol>
Perhatikan gambar berikut.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqcoVm9J3BNPZ3AUlZUrVI7efE1a9xMA2SVLhe7q8h1Xy2AGZzxLCer1o9IVQIlWaMiZdrnmMozXaMQZsDp5RH66VKDfNHZlqBgDPyusQW0lgidrgPVTdaIEJg4zyrpHuJhXf7qrZLVxUX/s1600/g5495.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="448" data-original-width="496" height="288" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqcoVm9J3BNPZ3AUlZUrVI7efE1a9xMA2SVLhe7q8h1Xy2AGZzxLCer1o9IVQIlWaMiZdrnmMozXaMQZsDp5RH66VKDfNHZlqBgDPyusQW0lgidrgPVTdaIEJg4zyrpHuJhXf7qrZLVxUX/s320/g5495.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
Barisan kotak-kotak berwarna biru menyatakan array ar, sementara batangan berwarna merah menunjukkan segment tree dengan cakupan yang dilingkupi. Setiap batang akan menyimpan nilai minimal dari nilai-nilai yang dilingkupinya. Misalnya batangan 0 menyimpan nilai minimal dari indeks 0 sampai 7, atau batangan 4 yang menyimpan nilai minimal dari indeks 2 sampai indeks 3. Istilah batang/batangan dapat juga disebut "segmen".<br />
<br />
Dengan begitu, ketika ditanya berapa nilai minimal dari indeks 2 sampai 7, cukup cari<br />
nilai terkecil antara batangan 4 dan batangan 2. Perhatikan bahwa keseluruhan elemen dari indeks 2 sampai 7 tercakupi oleh batangan 4 dan 2.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgghTxHETDF0umAbT2QCYOnWT5F2NafARBcComUUW0F0LIs5Ix1Z935nH0yUZ_ieol8Ed5PvAx-CzJXI2h6j8iCtIAdX7d6BeKu9haEOBqazLVDocbrRUWOaG0yvoQjM-NyvuB2clz_dgzF/s1600/g6209.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="448" data-original-width="496" height="289" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgghTxHETDF0umAbT2QCYOnWT5F2NafARBcComUUW0F0LIs5Ix1Z935nH0yUZ_ieol8Ed5PvAx-CzJXI2h6j8iCtIAdX7d6BeKu9haEOBqazLVDocbrRUWOaG0yvoQjM-NyvuB2clz_dgzF/s320/g6209.png" width="320" /></a></div>
<br />
Demikian pula bila ditanya berapa nilai minimal dari indeks 1 sampai 4, cukup cari nilai terkecil antara batangan 8, 4, dan 11.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgM7tCJa9rEGtqWTE8hXJFzn82GGnPEJkXDQ-9_elAAz8cE0wTLUEZf4zBEYqgQ9nBei9BVR0A8Fs7qInWmbfxwhBWGvNGrnI5RrmugAuh4e_9jSZ2uIkb6U1KH0pKVmGAphaihRGmBpCa0/s1600/g4764.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="448" data-original-width="496" height="289" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgM7tCJa9rEGtqWTE8hXJFzn82GGnPEJkXDQ-9_elAAz8cE0wTLUEZf4zBEYqgQ9nBei9BVR0A8Fs7qInWmbfxwhBWGvNGrnI5RrmugAuh4e_9jSZ2uIkb6U1KH0pKVmGAphaihRGmBpCa0/s320/g4764.png" width="320" /></a></div>
<br />
Sistem ini menjamin bahwa setiap interval yang ditanyakan dapat dicakupi oleh maksimal O(log N) batangan (dengan N adalah besarnya array). Oleh karena itu, setiap query dapat dijawab dalam O(log N).<br />
<br />
Lebih jauh lagi, setiap operasi update juga dapat dilakukan dalam O(log N). Maksimal hanya log N batangan yang perlu diperbaharui nilainya.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3vSe3opph5VtOQy2mFGhVL82Mstg3A7gUXI6ed1V2iCcnOnvIJtgLtdB2zWYlUOYhbxviGRLV36ZgUdDjYLo6By25SFrMt7ypovPFi05Iu0XwAXAgxmKfhvJQxfyjfVLobfpjbjQRepMr/s1600/g4844-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="448" data-original-width="496" height="289" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3vSe3opph5VtOQy2mFGhVL82Mstg3A7gUXI6ed1V2iCcnOnvIJtgLtdB2zWYlUOYhbxviGRLV36ZgUdDjYLo6By25SFrMt7ypovPFi05Iu0XwAXAgxmKfhvJQxfyjfVLobfpjbjQRepMr/s320/g4844-2.png" width="320" /></a></div>
<br />
Misalkan kita mengubah nilai ar[5]. Batangan nomor 12 jelas nilainya berubah menjadi nilai ar[5]. Kita juga perlu memperbaharui nilai batangan 5, batangan 2, dan batangan 0. Caranya:<br />
<ol>
<li>Nilai batangan 5: nilai terkecil antara batangan 11 dan batangan 12</li>
<li>Nilai batangan 2: nilai terkecil antara batangan 5 dan batangan 6</li>
<li>Nilai batangan 0: nilai terkecil antara batangan 1 dan batangan 2</li>
</ol>
Pembaharuan nilai batangan yang mencakup ar[5] tidak perlu dilakukan dengan linear search untuk mencari nilai terkecil. Kita cukup memperbaharui batangan dari kecil ke besar, sambil memanfaatkan informasi dari batangan yang lebih kecil. Inilah konsep kedua yang penting, yaitu adalah kemampuan untuk memperbaharui nilai batangan berdasarkan informasi dari dua batangan yang lebih kecil.<br />
<br />
<hr />
<h3>
Implementasi</h3>
Segment tree dapat diimplementasikan menjadi sebuah binary tree, yang mana setiap batangan akan kita sebut sebagai node. Anak dari suatu node selalu dua. Node paling atas disebut dengan root, dan node yang tidak memiliki anak lagi disebut leaf. Perhatikan ilustrasi berikut.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6FqiVQyf5Z_ho2qZkhG1jTJtCA6o0ErPdRFAdprxnU8KqaMj4DuzJgIck7ZevTGUGYXwjJjZVP4-IY27ox5vmlCGhgbuPQizOgYswCgS18IV-MCihqy-yEX4tAEnqv9JzHXmcqCdvViMu/s1600/g7400.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="185" data-original-width="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6FqiVQyf5Z_ho2qZkhG1jTJtCA6o0ErPdRFAdprxnU8KqaMj4DuzJgIck7ZevTGUGYXwjJjZVP4-IY27ox5vmlCGhgbuPQizOgYswCgS18IV-MCihqy-yEX4tAEnqv9JzHXmcqCdvViMu/s1600/g7400.png" /></a></div>
<br />
Dengan struktur ini, dijamin bahwa setiap node bernomor i memiliki anak bernomor 2i+1 dan 2i+2. Hal ini mempermudah kita dalam penyimpanan data karena kita tidak perlu mengimplementasikan tree dengan representasi graf (misal: adjacency list). Untuk contoh di atas, kita cukup gunakan array dengan ukuran 15 elemen. Akses anak dari suatu node i dilakukan dengan mengambil indeks 2i+1 dan 2i+2.<br />
<br />
Catatan: apabila array dimulai dari angka 1, berarti anak node i adalah 2i dan 2i+1.<br />
<br />
Lalu berapa banyak node yang dibutuhkan segment tree untuk N elemen?<br />
Bila ditelusuri dari root ke leaf, yang dibutuhkan adalah 1+2+4+8+...+2<sup>D</sup> sehingga 2<sup>D</sup> lebih dari atau sama dengan N. Jumlahan deret itu sama dengan 2<sup>D+1</sup>-1.<br />
<br />
Jadi cukup cari bilangan 2 pangkat yang lebih atau sama dengan N, lalu dikali 2 saja. Nilai ini paling besar mendekati 4N, sehingga memori yang diperlukan adalah O(N).<br />
<br />
Kalau Anda sering ikut kontes programming dan butuh cepat-cepat, angka perlu dihafal adalah:<br />
<ul>
<li>131.072 (2<sup>17</sup>), untuk N=100.000. Saya biasa ingatnya 132.000 saja.</li>
<li>1.048.576 (2<sup>20</sup>), untuk N=1.000.000. Saya biasa ingatnya 1.050.000 saja.</li>
</ul>
<br />
Kembali ke implementasi segment tree, pertama buat dulu array global untuk menampung nilai setiap batangan. Anggap saja kali ini N=100.000.<br />
<div>
<script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
const int MAXN = 132000;
int stri[2*MAXN];
]]></script><!-----></div>
<br />
Selanjutnya, kita bisa menulis prosedur untuk membangun segment tree untuk pertama kalinya.<br />
Variabel id menyatakan indeks batangan yang akan dibuat, sementara l dan r menyatakan cakupan batangan. Prosedur perlu dipanggil dengan "build(0, 0, N-1)", sebab batangan pertama memiliki indeks 0, dan mencakup indeks 0 sampai N-1.<br />
<div>
<script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
void build(int id, int l, int r) {
if (l == r) {
// Saat rentang yang dicakup batangan ini sama dengan 1 elemen,
// nilai terkecilnya sama dengan elemen tersebut
sTree[id] = ar[l];
} else {
// Ambil nilai tengah rentang yang dicakup
int m = (l + r)/2;
// Secara rekursif, bangun anak-anaknya yang membagi 2 cakupan
build(2*id + 1, l, m);
build(2*id + 2, m+1, r);
// Cari nilai batangan ini dari kedua anaknya
sTree[id] = min(sTree[2*id + 1], sTree[2*id + 2]);
}
}
]]></script><!-----></div>
<br />
Untuk implementasi query, kita dapat melakukannya secara rekursif dari node 0. Apabila node saat ini <b>dikandung</b> oleh rentang diinginkan, cukup kembalikan nilai yang disimpan pada node tersebut. Selain daripada itu, pecah menjadi 2 dan kumpulkan jawaban dari anak-anaknya yang menyentuh rentang yang diinginkan. Pemanggilan pertama dapat dilakukan dengan "query(0, 0, N-1, x, y)".<br />
<div>
<script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
// Cari nilai terkecil antara ar[x], ar[x+1], ar[x+2], ..., ar[y]
int query(int id, int l, int r, int x, int y) {
if ((x <= l) && (r <= y)) {
// Batangan ini sepenuhnya berada di antara x dan y
// Cukup kembalikan nilai yang disimpan batangan
return sTree[id];
} else {
// Batangan ini tidak sepenuhnya berada di antara x dan y, jadi perlu ditelusuri
int m = (l + r)/2;
// Cari jawaban pada anaknya yang mencakup [x, y]
int ans = INF;
if (x <= m) ans = min(ans, query(2*id + 1, l, m, x, y));
if (y > m) ans = min(ans, query(2*id + 2, m+1, r, x, y));
return ans;
}
}
]]></script><!-----></div>
<br />
Terakhir, untuk update lakukan juga secara rekursif dari node 0 sampai leaf tercapai. Jangan lupa untuk memperbaharui nilai batangannya setelah anaknya di-update. <br />
<div>
<script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
// Ubah ar[x] = v
void update(int id, int l, int r, int x, int v) {
if (l == r) {
sTree[id] = v;
} else {
int m = (l + r)/2;
// Jalankan update pada batangan yang mencakup x saja
if (x <= m) update(2*id + 1, l, m, x, v);
if (x > m) update(2*id + 2, m+1, r, x, v);
// Perbaharui nilai batangan ini
sTree[id] = min(sTree[2*id + 1], sTree[2*id + 2]);
}
}
]]></script><!-----></div>
<br />
Dan selesailah implementasinya. Berikut implementasi lengkapnya.<br />
<div>
<script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
#include <cstdio>
#include <algorithm>
using namespace std;
const int minN = 132000;
const int INF = 2123123123;
int ar[minN];
int sTree[2*minN];
void build(int id, int l, int r) {
if (l == r) {
// Saat rentang yang dicakup batangan ini sama dengan 1 elemen,
// nilai terkecilnya sama dengan elemen tersebut
sTree[id] = ar[l];
} else {
// Ambil nilai tengah rentang yang dicakup
int m = (l + r)/2;
// Secara rekursif, bangun anak-anaknya yang membagi 2 cakupan
build(2*id + 1, l, m);
build(2*id + 2, m+1, r);
// Cari nilai batangan ini dari kedua anaknya
sTree[id] = min(sTree[2*id + 1], sTree[2*id + 2]);
}
}
// Cari nilai terkecil antara ar[x], ar[x+1], ar[x+2], ..., ar[y]
int query(int id, int l, int r, int x, int y) {
if ((x <= l) && (r <= y)) {
// Batangan ini sepenuhnya berada di antara x dan y
// Cukup kembalikan nilai yang disimpan batangan
return sTree[id];
} else {
// Batangan ini tidak sepenuhnya berada di antara x dan y, jadi perlu ditelusuri
int m = (l + r)/2;
// Cari jawaban pada anaknya yang mencakup [x, y]
int ans = INF;
if (x <= m) ans = min(ans, query(2*id + 1, l, m, x, y));
if (y > m) ans = min(ans, query(2*id + 2, m+1, r, x, y));
return ans;
}
}
// Ubah ar[x] = v
void update(int id, int l, int r, int x, int v) {
if (l == r) {
sTree[id] = v;
} else {
int m = (l + r)/2;
// Jalankan update pada batangan yang mencakup x saja
if (x <= m) update(2*id + 1, l, m, x, v);
if (x > m) update(2*id + 2, m+1, r, x, v);
// Perbaharui nilai batangan ini
sTree[id] = min(sTree[2*id + 1], sTree[2*id + 2]);
}
}
int main() {
int N = 8;
ar[0] = 4;
ar[1] = 2;
ar[2] = 5;
ar[3] = 2;
ar[4] = 1;
ar[5] = 8;
ar[6] = 3;
ar[7] = 6;
build(0, 0, N-1);
printf("%d\n", query(0, 0, N-1, 0, 2)); // Cetak 2
printf("%d\n", query(0, 0, N-1, 1, 3)); // Cetak 2
printf("%d\n", query(0, 0, N-1, 2, 2)); // Cetak 5
printf("%d\n", query(0, 0, N-1, 0, 7)); // Cetak 1
update(0, 0, N-1, 4, 3);
printf("%d\n", query(0, 0, N-1, 0, 7)); // Cetak 2
}
]]></script><!-----></div>
<br />
<hr />
<h3>
Latihan</h3>
Untuk latihan segment tree klasik, coba kerjakan <a href="https://www.spoj.com/problems/THRBL/">SPOJ THRBL</a>.<br />
Ada sedikit jebakan-jebakan pada cara menerjemahkan inputnya ke query, tetapi konsep segment tree-nya klasik.<br />
<br />
<hr />
<h3>
Abstraksi Segment Tree</h3>
Setelah Anda menguasai konsep segment tree, akan tiba saatnya Anda perlu segment tree yang aneh-aneh. Misalnya perlu menyimpan lebih dari 1 nilai.<br />
<br />
Secara umum, template abstrak berikut dapat digunakan:<br />
<div>
<script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
void build(int id, int l, int r) {
if (l == r) {
// Isi leaf node untuk segmen yang mencakup 1 elemen (yaitu ar[l])
sTree[id] = CREATE(ar[l])
} else {
int m = (l + r)/2;
// Secara rekursif, bangun anak-anaknya
build(2*id + 1, l, m);
build(2*id + 2, m+1, r);
// Cari nilai batangan ini dari kedua anaknya
sTree[id] = MERGE(sTree[2*id+1], sTree[2*id+2])
}
}
// Cari nilai terkecil antara ar[x], ar[x+1], ar[x+2], ..., ar[y]
TYPE query(int id, int l, int r, int x, int y) {
if ((x <= l) && (r <= y)) {
// Batangan ini sepenuhnya berada di antara x dan y
// Cukup kembalikan nilai yang disimpan batangan
return sTree[id];
} else {
// Batangan ini tidak sepenuhnya berada di antara x dan y, jadi perlu ditelusuri
int m = (l + r)/2;
// Cari jawaban pada anaknya yang mencakup [x, y]
TYPE ans = NULL;
if (x <= m) ans = MERGE(ans, query(2*id + 1, l, m, x, y));
if (y > m) ans = MERGE(ans, query(2*id + 2, m+1, r, x, y));
return ans;
}
}
// Ubah ar[x] = v
void update(int id, int l, int r, int x, int v) {
if (l == r) {
sTree[id] = CREATE(v);
} else {
int m = (l + r)/2;
// Jalankan update pada batangan yang mencakup x saja
if (x <= m) update(2*id + 1, l, m, x, v);
if (x > m) update(2*id + 2, m+1, r, x, v);
// Perbaharui nilai batangan ini
sTree[id] = MERGE(sTree[2*id + 1], sTree[2*id + 2]);
}
}
]]></script><!-----></div>
<br />
Selama Anda bisa mendefinisikan fungsi:<br />
<ul>
<li>CREATE(v): menciptakan sebuah node segment tree dari nilai sebuah elemen array berupa v</li>
<li>MERGE(vl, vr): menggabungkan dua node segment tree yang bersebelahan menjadi sebuah node yang lebih besar</li>
</ul>
<div>
maka Anda dapat membuat segment tree untuk kasus yang lebih kompleks. Pada contoh yang kali ini kita bahas, definisinya adalah:</div>
<div>
<ul>
<li>CREATE(v): cukup kembalikan nilai v. Sebab nilai terkecil untuk sebuah elemen array berupa v adalah v itu sendiri.</li>
<li>MERGE(vl, vr): kembalikan nilai terkecil antara vl dan vr. Sebab nilai terkecil dari array dengan indeks yang dicakup node berisi vl dan vr jelas adalah nilai terkecil dari keduanya.</li>
</ul>
</div>
<br />
Pada kasus segment tree yang menyimpan lebih dari 1 nilai, CREATE dan MERGE menjadi tidak sesederhana yang kita bahas di sini. Coba baca tulisan saya sebelumnya tentang <a href="https://kupaskode.blogspot.com/2013/07/tentang-segment-tree-variasi-nilai.html">variasi nilai agregat</a>.<br />
<br />
Konsep CREATE dan MERGE ini sangat erat dengan konsep divide and conquer.<br />
<br />
Bagian yang rawan bug adalah bagian pemeriksaan rentangnya, yang digunakan pada kondisi if. Salah mengetikkan batasannya dapat menghasilkan bug yang susah dideteksi. Jadi pahami betul-betul bagian sana.<br />
<br />
Ada 1 bagian yang belum saya bahas di sini, yaitu range update. Implementasi range update perlu dibuat dengan "lazy propagation". Kita akan membahasnya dan abstraksinya pada <a href="https://kupaskode.blogspot.com/2019/01/petunjuk-coding-segment-tree-lazy-propagation.html">kesempatan yang akan datang</a>.<br />
<br />
<hr />
<hr />
<h3>
Penutup</h3>
Perkenalan ini lebih dititikberatkan pada cara menulis kodenya yang berlaku secara umum, sehingga kerangka kodenya dapat dipakai untuk kasus segment tree lainnya.<br />
<br />
Saat saya pertama kali coding segment tree, saya bingung karena penulisan kode di buku CP, dari asisten TOKI, atau sumber internet berbeda-beda. Diperlukan ekstra usaha untuk mendekomposisi apa saja yang perlu dilakukan saat build, query, dan update.<br />
<br />
Dengan tulisan ini saya harap pembaca jadi tahu komponen abstrak dari segment tree itu apa saja dan menerapkannya pada macam-macam masalah yang dihadapi.<br />
<br />William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-61666713545180910512018-08-20T12:59:00.000+07:002020-03-29T23:43:05.330+07:00Strategi Belajar CPKalau diingat-ingat, saya menghabiskan waktu cukup lama di pemrograman kompetitif (alias Competitive Programming/CP). Mulai dari 2009 sampai dengan 2015, yang mana sesudah itu saya pensiun. Selama 7 tahun tersebut saya menghabiskan banyak waktu untuk berlatih.<br />
<br />
Dari beberapa cara berlatih, ada yang lebih efisien atau kurang. Mungkin bisa dianggap seperti sistem "experience" atau exp pada game. Misalnya pada game pokemon, mengalahkan dragonite memberikan exp jauh lebih banyak dari magikarp. Seandainya sejak awal saya tahu cara berlatih yang paling efisien, mungkin dengan 7 tahun saya bisa lebih jago dari saya yang sekarang.<br />
<br />
Melalui tulisan ini, saya akan berbagi informasi cara berlatih berdasarkan pengalaman saya. Cara berlatih ini tidak saling lepas, jadi bisa dikombinasikan sesuai kebutuhan. Semoga dapat membantu kalian yang sedang menempuh perjalanan menuju OSN, IOI, regional ICPC, atau ICPC world final.<br />
<br />
<hr />
<h3>
Latihan NonKontes (UVa, SPOJ)</h3>
<div>
Saya memulai pembelajaran CP dari latihan UVa. Setiap harinya, saya memilih beberapa soal secara acak lalu dikerjakan. Biasanya saya dapat menyelesaikan 3 soal dalam sehari. Setelah mengenal SPOJ, saya mulai berlatih di sana dengan menonton papan submission, dan memilih soal yang berkode menarik.<br />
<br />
Ciri utama dari latihan nonkontes adalah tidak adanya batas waktu dan kebebasan dalam memilih soal. Anda boleh memilih soal sendiri, dan dikerjakan dalam jangka waktu yang ditentukan sendiri pula.<br />
<a name='more'></a><br />
Dalam hal memilih soal, ada yang perlu diperhatikan. Ribuan soal yang ada di online judge diciptakan secara berbeda. Ada soal yang bagus, dan ada juga yang kurang bagus. Sepengalaman saya, soal yang bagus adalah soal yang:<br />
<ul>
<li>Memerlukan analisis mendalam, tidak straight-forward, atau begitu dibaca langsung terpikirkan solusinya.<br />
Contohnya adalah soal-soal IOI, yang mana diperlukan beberapa observasi terlebih dahulu sebelum akhirnya solusi ditemukan. Contoh soal yang tidak memerlukan analisis mendalam adalah soal-soal klasik, misalnya mencari pemasangan node-node pada graf yang dapat diselesaikan dengan Edmond's Blossom.</li>
<li>Tidak menimbulkan rasa malas untuk menuliskan kodenya.<br />
Contoh soal yang menimbulkan rasa malas adalah soal yang tingkat kesulitannya "ditambah-tambah" atau yang deskripsi soalnya sengaja menyembunyikan fakta. Rasanya soal semacam ini dibuat hanya untuk memuaskan ego penciptanya.</li>
</ul>
Kembali ke persoalan memilih soal, saya sering menemukan soal yang kurang bagus (terutama di UVa dan SPOJ). Bukan berarti online judge tersebut jelek, karena banyak pula soal bagus di UVa dan SPOJ. Saya menyarankan untuk mengerjakan soal yang sudah dipaket atau direkomendasikan orang. Misalnya kalau di UVa, kerjakanlah soal dengan kode prefix 2 dan 3 digit. Soal-soal tersebut berasal dari regional ICPC yang modern, ketimbang soal berkode prefix 1 digit yang sudah ketinggalan zaman. Contoh soal paketan lainnya adalah rekomendasi soal yang diberikan di buku pembelajaran, misalnya buku Competitive Programming 3 oleh Steven Halim & Felix Halim.</div>
<div>
<br /></div>
<div>
Setelah terarah dalam memilih soal, komponen berikutnya adalah batas waktu pengerjaan. Pertanyaan yang muncul adalah:<br />
<blockquote class="tr_bq">
"apakah kalau saya tidak menemukan solusinya, saya boleh mencarinya di internet?"</blockquote>
Jawaban yang saya temukan adalah: ya.<br />
<br />
Mencari solusi di internet ketika tersendat bukan berarti kita lemah. Justru sebaliknya, kita mengakui bahwa kemampuan kita masih terbatas dan mau berusaha belajar untuk melampaui batas tersebut. Mengakui kekurangan diri sendiri adalah tanda kedewasaan.<br />
<br />
Berikanlah batas waktu memikirkan solusi, misalnya kalau saya selama 1 minggu. Selama 1 minggu itu, usahakanlah memikirkan solusi setiap ada waktu luang. Kalau sudah lewat batas waktu dan masih belum juga terpikirkan, berarti sudah saatnya mencari jawaban.<br />
<br />
Bisa jadi, alasan kita tidak dapat menyelesaikan soal itu bukan karena kita yang lemah. Melainkan ada teori khusus yang belum diketahui. Sebagai contoh, mengerjakan soal klasik seperti Minimum Spanning Tree tanpa mengetahui algoritmanya (misal: Kruskal) sama saja dengan mau menciptakan algoritma itu dari 0, yang mana kurang realistis untuk durasi kontes selama beberapa jam atau bahkan 1 minggu.<br />
<br />
Setiap kali kejadian seperti ini dialami, kita memperkaya ilmu. Misalnya setelah membaca solusi, kita menjadi sadar "oh ternyata ada ya yang namanya MST". Selanjutnya tinggal dipelajari dan bertambahlah ilmu kita. Hampir semua ilmu baru saya dapatkan karena kejadian semacam ini.<br />
<br />
Kalaupun ternyata bukan karena teori khusus yang belum diketahui, 1 minggu dipenuhi bertapa itu pastinya telah memberikan manfaat. Menghabiskan waktu begitu lama untuk memikirkan solusi suatu soal melatih kita untuk melihat soal dari berbagai sudut pandang dan mengumpulan observasi. Pada akhirnya setelah solusi dibaca, mungkin yang terjadi adalah "ooooh begitu ternyata". Meskipun solusi tidak kita temukan sendiri, pengalaman mencari solusi telah meningkatkan kemampuan Anda.<br />
<br />
<hr />
<h3>
Latihan Kontes (Codeforces, SRM Topcoder)</h3>
Setelah cukup percaya diri (mulai sering AC di soal nonkontes), beranjaklah ke latihan kontes. Zaman dulu, ada SRM (Single Round Match) yang diadakan Topcoder setiap 2 minggu sekali. Beberapa tahun kemudian, muncullah Codeforces yang mengadakan kontes dengan frekuensi lebih tinggi. Yang manapun kontesnya, manfaat yang dirasakan secara umum serupa.<br />
<br />
Ciri utama dari kontes adalah terbatasnya waktu, adanya pesaing, dan adanya pembahasan di akhir kontes. Seluruh ciri ini membuat latihan kontes sangat penting dalam melatih perkembangan CP Anda.<br />
<br />
Waktu yang terbatas mencerminkan kondisi kompetisi yang sesungguhnya. Sering berlatih dengan waktu terbatas akan mengurangi kegugupan dan stress saat kompetisi. Saya juga merasakan ketika berada di bawah tekanan, saya bisa jadi "berserk" dan melampaui batas kemampuan biasanya.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMuDC4fBS2vqW3FcYU7VG13ti3vQlYbJso-WHAl8h0RI3sfqpSGp5uQlEQT4JXtXInPdEUBjpfMTUq0z4iVMdv6Ai4WyiJY2gsANSfsM-7h5eN77Cn1ohAPAM0pyXIQx4uMRoPfIRZxQ0P/s1600/berserk.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="258" data-original-width="350" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMuDC4fBS2vqW3FcYU7VG13ti3vQlYbJso-WHAl8h0RI3sfqpSGp5uQlEQT4JXtXInPdEUBjpfMTUq0z4iVMdv6Ai4WyiJY2gsANSfsM-7h5eN77Cn1ohAPAM0pyXIQx4uMRoPfIRZxQ0P/s1600/berserk.png" /></a></div>
<br />
Keberadaan pesaing bermanfaat untuk memicu semangat untuk latihan. Selain itu, kita juga memiliki kesempatan untuk melihat kode yang mereka tulis. Pada SRM atau Codeforces, terdapat sistem yang memungkinkan Anda melihat kode orang lain dan mencari kasus uji maut. Kalau program mereka gagal memberikan solusi yang benar, Anda mendapatkan nilai.<br />
<br />
Membaca kode orang lain bermanfaat untuk:<br />
<ul>
<li>Belajar cara lain menuliskan algoritma. Sering kali saya menemukan cara-cara menarik untuk mengimplementasikan sesuatu. Dikatakan menarik karena penulisannya lebih pendek, lebih kebal bug, atau sekedar lebih elegan. Contoh nyatanya adalah treap yang saya adopsi <a href="https://kupaskode.blogspot.com/2013/12/treap-alternatif-balanced-binary-search.html">di sini</a>. Ketika saya Pelatnas 1, Pak Suhendry bilang bahwa beberapa kali ikut kontes (dan membaca kode orang lain) akan meningkatkan kemampuan implementasi algoritma secara pesat.</li>
<li>Belajar memahami jalan pikir orang lain. Kemampuan ini dibutuhkan untuk kontes bertim seperti ICPC. Selain itu, kita juga lebih mandiri dalam arti mengerti solusi cukup dari kode saja. Beberapa kali saya tersendat dalam soal dan memutuskan untuk mencari solusi di internet, tetapi yang didapatkan hanya kode solusinya saja dari web Rusia atau Cina.</li>
</ul>
<div>
Belajar dari orang lain merupakan praktik yang umum di dunia kompetitif bidang manapun. Sebagai contoh, salah satu latihan pemain e-sports profesional adalah menonton dan menganalisis <i>replay</i> permainan kompetisi sebelumnya. Yang perlu ditekankan adalah kita harus menghilangkan perasaan ego ataupun minder dan fokus pada proses pembelajaran.</div>
<div>
<br /></div>
Terakhir, biasanya kontes diakhiri dengan pembahasan solusi. Untuk setiap soal yang tidak berhasil diselesaikan, bacalah solusinya (baik dalam bentuk narasi atau kode) dan kerjakan sampai AC. Saya sering menyebutnya dengan "balas dendam", sementara Ashar bilang istilah tepatnya adalah "upsolving". Apapun istilahnya, balas dendam ini adalah bagian terpenting! Di sinilah kita meningkatkan batas atas kemampuan, dengan menyelesaikan soal yang tidak bisa diselesaikan sebelumnya. Dengan selalu mengikuti kontes dan balas dendam, dijamin dalam beberapa periode kontes kemampuan Anda akan meningkat pesat.<br />
<br />
Jika Anda belum merasa percaya diri untuk ikut kontes sungguhan, Codeforces memiliki fitur untuk memulai kontes bayangan. Anda dapat mengerjakan kontes dan mengetahui berapa peringkat Anda dibandingkan dengan peserta yang sebelumnya resmi mengikuti kontes itu.<br />
<br />
<hr />
<h3>
Latihan Tim</h3>
Untuk kompetisi berkelompok seperti ICPC, diwajibkan untuk berlatih bersama anggota tim.<br />
<br />
Latihan ini berguna untuk mengerti kelebihan dan kekurangan anggota tim lainnya. Kita jadi tahu kalau menemukan soal bertipe X, maka siapa yang kira-kira mampu menyelesaikannya. Lama kelamaan, setiap anggota menjadi spesialis untuk jenis soal tertentu.<br />
<br />
Dengan latihan tim, kita juga terlatih untuk bekerja sama. Meskipun memiliki kemampuan individual yang baik, tanpa kerja sama tim mungkin saja tim tersebut kalah dari tim berkemampuan individual rendah tetapi kerja samanya baik. Kerja sama yang dimaksud adalah tahu kapan waktunya untuk berdiskusi, bagaimana mempercayai anggota tim lain, atau mengatasi stress ketika terus-terusan tidak AC. Baca selebihnya tentang kompetisi bertim di tulisan <a href="https://kupaskode.blogspot.com/2018/06/strategi-kontes-icpc.html">strategi kontes ICPC</a>.<br />
<br />
Untuk persiapan ICPC, tim saya sering berlatih secara rutin dengan memilih set soal di gym Codeforces. Set soal yang saya sukai adalah seri Petrozavodsk yang berasal dari Rusia, salah satu contohnya: <a href="https://codeforces.com/gym/101741">https://codeforces.com/gym/101741</a>. Terdapat pula set soal regional ICPC Eropa dan Asia yang kualitas soalnya bagus.<br />
<br /></div>
<hr />
<h3>
Latihan Intensif</h3>
<br />
Latihan ini berbentuk sekumpulan individu atau tim menjalani latihan dalam beberapa hari. Contoh umum untuk latihan seperti ini adalah Pelatnas TOKI, yang mana orang-orang dikumpulkan di suatu tempat dan dilatih selama 3-4 minggu. Selain Pelatnas, saya juga pernah mengikuti latihan "furious" yang merupakan inisiatif dari Pak Denny sebagai pelatih ICPC di kampus.<br />
<br />
Bentuk latihan ini bisa beragam, seperti ada sesi pengajaran, presentasi, dan latihan. Bentuk paling sederhananya adalah mengerjakan kontes tanpa diskusi dari pagi sampai siang, lalu siangnya istirahat, dilanjutkan dengan mengerjakan set soal yang sama tetapi boleh berdiskusi.<br />
<br />
Meskipun terdengar membosankan dan melelahkan, saya merasa latihan intensif ini dapat meningkatkan kemampuan secara pesat. Alasannya adalah dengan adanya orang lain, kita menjadi bersaing (yang sehat) dan bisa saling belajar satu sama lain. Kadang-kadang, semua peserta menjadi bersatu dalam menghadapi soal yang sulit. Hal ini pernah terjadi sebelumnya pada kontes furious, yang mana kita bersatu untuk memahami bagaimana <a href="https://kupaskode.blogspot.com/2017/07/range-query-bit.html">range query BIT bekerja</a>.<br />
<br />
<hr />
<h3>
Perkakas Latihan</h3>
Untuk memulai latihan, Anda dapat menggunakan fasilitas berikut.<br />
<br />
<a href="https://tlx.toki.id/"><b>TLX</b></a><br />
Sebagai media pembelajaran dari 0. Terdapat kursus pemrograman dasar dan pemrograman kompetitif dasar. Mampu menyelesaikan kursus pemrograman kompetitif dasar akan memberikan dasar ilmu yang Anda butuhkan untuk belajar mandiri secara efektif.<br />
<br />
<b><a href="https://codeforces.com/gyms">Gym Codeforces </a></b><br />
Situs yang kaya akan soal-soal menarik. Sejauh ini saya paling menyukai kontes-kontes yang berasal dari Petrozavodsk, ICPC Regional Eropa dari berbagai penjuru (NEERC, NWERC, SWERC), dan ICPC Regional kawasan tepi Pasifik (Asia Timur dan Tenggara).<br />
<br />
<b><a href="http://uhunt.felix-halim.net/">uhunt</a></b><br />
Situs yang dibuat Felix Halim untuk "berburu" soal UVa. Terdapat fitur-fitur navigasi di bagian bawah untuk memilih soal dan melihat tingkat kesulitannya (persentase AC dari seluruh pengumpulan). Rasanya soal yang memiliki kode prefix 1 digit (v1 sampai dengan v9) sudah ketinggalan zaman, karena berasal dari belasan tahun yang lalu. Maksudnya ketinggalan zaman adalah soal tersebut kurang memenuhi 2 syarat soal bagus yang saya sebutkan di atas. Kerjakanlah soal dengan kode prefix 2 dan 3 digit, yang sebagian besar merupakan soal yang bagus.<br />
<br />
<b><a href="https://vjudge.net/">vjudge</a></b><br />
Merupakan situs umum untuk membuat kontes dari berbagai online judge (UVa, SPOJ, ICPC Live Archive, Codeforces). Anda dapat memilih beberapa soal, mengatur waktu kontes, dan memasukkan peserta-peserta ke kontes yang Anda buat. Berguna apabila Anda adalah seseorang yang sedang melatih.<br />
Saya suka menggunakan vjudge.<br />
<br />
<hr />
<h3>
Penutup</h3>
Demikianlah pengalaman berlatih yang saya alami. Apabila Anda masih memiliki waktu yang panjang untuk berlatih, selamat berlatih seefektif mungkin dan dapatkan experience setinggi mungkin untuk naik ke level selanjutnya.William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com5tag:blogger.com,1999:blog-2894426456296176040.post-21695959880794906942018-06-23T10:30:00.000+07:002018-06-23T23:08:18.852+07:00Lazy Segment Tree (Bukan Lazy Propagation)<div dir="ltr" style="text-align: left;" trbidi="on">Belakangan ini saya jadi sering menulis tentang segment tree. Berhubung masih terpikirkan, saya akan terus membagikan ilmu-ilmu aneh tentang segment tree.<br />
<br />
Yang kali ini adalah lazy segment tree, yang berbeda dengan <a href="http://kupaskode.blogspot.com/2013/07/tentang-segment-tree-lazy-propagation.html">lazy propagation</a>. Saya pernah menghadapi soal semacam ini, seingat saya di codeforces. Namun sayangnya tidak bisa saya temukan lagi padahal sudah mengorek-ngorek submission lama.<br />
<br />
Inti soalnya adalah:<br />
<ul style="text-align: left;"><li>Terdapat sebuah array dengan M elemen, yang mana M bisa sebesar 2 milyar.</li>
<li>Pada awalnya setiap elemen bernilai 0.</li>
<li>Terdapat N operasi berupa:</li>
<ul><li>U x y: artinya ubah elemen array di posisi ke-x menjadi y.</li>
<li>Q x y: artinya cari elemen terbesar yang berada di antara elemen ke-x dan ke-y.</li>
</ul><li>Anda diwajibkan untuk mengerjakan operasi secara online (operasi berikutnya tidak dapat dibaca sebelum operasi saat ini dikerjakan) </li>
</ul>Untuk menjamin soal diselesaikan secara online, si pembuat soal mengenkripsi operasi selanjutnya dengan jawaban dari operasi 'Q' yang terakhir ditanyakan (atau 0, apabila tidak ada operasi 'Q' sebelumnya).<br />
<br />
Seandainya soal ini boleh dikerjakan secara offline, solusinya sesederhana membaca seluruh query terlebih dahulu lalu grid compression. Meskipun terdapat 2 milyar elemen, sebenarnya hanya paling banyak 2N elemen yang mungkin disebut dalam operasi-operasinya.<br />
<br />
<hr /><h3>Solusi Binary Search Tree</h3><div>Solusi langsung terpikirkan adalah menggunakan BST.</div><div><br />
</div><div>Cukup simpan elemen BST berupa pasangan data <indeks, nilai>. BST ini terurut berdasarkan indeks elemen. Setiap node perlu menyimpan nilai agregat berupa nilai terbesar antar elemen-elemen subtreenya. Kini seluruh operasi dapat dilakukan dalam O(log N).</div><div><br />
</div><div>Apabila Anda belum tahu apa itu BST, cek tulisan saya sebelumnya tentang <a href="http://kupaskode.blogspot.com/2013/12/treap-alternatif-balanced-binary-search.html">treap</a>.</div><div><br class="Apple-interchange-newline" /> <hr /><h3>Solusi Lazy Segment Tree</h3></div><div>Solusi alternatifnya adalah dengan segment tree yang node-nya bersifat "lazy". Setiap node hanya dibuat apabila dia akan diakses. Nilai agregat dari segment tree ini adalah nilai terbesar yang diwakilkan oleh suatu segmen.</div><div><br />
</div><div>Pada awalnya, kita hanya memiliki sebuah node yang mencakup elemen 1 sampai dengan M. Kita tidak perlu membuat anak kiri dan kanannya untuk saat ini. </div><div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgi76DIRh4eSUM87YRQYg2spZCF9mU3oSQkeDUfS4paOdiAGSCUUj_4SZJEzwdF1wbzO4Cp5qL4Pwba8R6-mN5zsZOu1t3IUSgIQgPxcJZKCTGV8DYVIOQPdGrsgNrKlkdYWHIv5DY9UFjD/s1600/lazy-root.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="141" data-original-width="550" height="102" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgi76DIRh4eSUM87YRQYg2spZCF9mU3oSQkeDUfS4paOdiAGSCUUj_4SZJEzwdF1wbzO4Cp5qL4Pwba8R6-mN5zsZOu1t3IUSgIQgPxcJZKCTGV8DYVIOQPdGrsgNrKlkdYWHIv5DY9UFjD/s400/lazy-root.png" width="400" /></a></div><br />
</div><div>Kemudian untuk operasi "U x y", lakukan perubahan pada elemen ke-x seperti segment tree pada umumnya. Berhubung bisa saja terdapat node yang belum dibuat, inilah waktunya untuk membuat node tersebut. Setiap kali kita mengunjungi sebuah node yang belum memiliki anak kiri dan kanan, buat kedua anak tersebut, lalu lanjutkan operasi ke salah satu anak yang bersangkutan.</div><a name='more'></a><br />
<br />
<div>Node yang dibuat ini dipastikan memiliki nilai agregat 0. Pada akhirnya, terbentuk jalur "root to leaf" yang terlihat seperti "pengeboran". Dijamin terdapat paling banyak O(log M) node yang dibuat untuk setiap aktivitas pengeboran.</div><div><br />
</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXyfRlAYet9cNEGRWkTe7vgPN_x6lV5kK-04hBSCWGVlHffCO8u67AGPXbgSJ8mDxEp5pxWkpcCgu_pVumJHN7QNkr-qe2rWvW5d1_Q8PDcZOquLT3Grx4akHfnRolR0r0ebZCA11XZBPa/s1600/lazy-update.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="250" data-original-width="550" height="181" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXyfRlAYet9cNEGRWkTe7vgPN_x6lV5kK-04hBSCWGVlHffCO8u67AGPXbgSJ8mDxEp5pxWkpcCgu_pVumJHN7QNkr-qe2rWvW5d1_Q8PDcZOquLT3Grx4akHfnRolR0r0ebZCA11XZBPa/s400/lazy-update.png" width="400" /></a></div><br />
</div><div><br />
</div><div>Untuk operasi "Q x y", lakukan hal yang serupa. Cukup lakukan operasi pencarian nilai terbesar seperti biasa sambil membuat node-node yang belum ada. Perhatikan bahwa bisa saja pembuatan node tidak sedalam sampai segmen yang mewakili 1 elemen; kita berhenti saat segmen yang saat ini dievaluasi berada di dalam cakupan [x, y]. </div><div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5pJ4CB3j6mwWcA3SzI0s5VbYK5wmWn0faGI2FoaU7tQya2NQJQwNZPXBdBMqaCyxKJqTmdVzpmw7AFDCX-ZuBEQISuhk9rXdxFwFcEISmW8bYRAUKUQEFJx93r75YeRfZ_du8HRimQ16L/s1600/lazy-query.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="250" data-original-width="550" height="181" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5pJ4CB3j6mwWcA3SzI0s5VbYK5wmWn0faGI2FoaU7tQya2NQJQwNZPXBdBMqaCyxKJqTmdVzpmw7AFDCX-ZuBEQISuhk9rXdxFwFcEISmW8bYRAUKUQEFJx93r75YeRfZ_du8HRimQ16L/s400/lazy-query.png" width="400" /></a></div><div style="text-align: center;">Node biru menunjukkan segmen yang diperlukan untuk menjawab "Q x y"</div></div><div><br />
</div><div>Lagi-lagi dijamin terdapat paling banyak O(log M) node yang dibuat. Jadi kebutuhan memori untuk melakukan seluruh operasi adalah O(N log M).</div><div><br />
</div><div>Dari segi kompleksitas waktu, jelas bahwa setiap operasi juga berlangsung dalam O(log M). Kompleksitas waktu solusi ini adalah O(N log M).</div><div><br />
</div><div>Untuk implementasinya, saya senang membuat penampungan node-node ketimbang menggunakan pointer. Penampungan node ini dapat dibuat dengan vektor, dan setiap node kini dapat direferensikan menggunakan indeksnya di vektor tersebut. Berikut contoh implementasinya (tanpa enkripsi operasi):</div><div><br />
</div><div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
class Node{
public:
int maxVal;
int l, r; // Indeks anak kiri dan kanan di penampungan
Node() : l(-1), r(-1), maxVal(0) {}
};
int N, M;
vector<node> nodes; // Penampungan node
void update(int idx, int l, int r, int x, int val) {
if (l == r) {
nodes[idx].maxVal = val;
} else {
int mid = (l + r) / 2;
// Jika anaknya belum dibuat, buat keduanya
if (nodes[idx].l == -1) {
nodes[idx].l = nodes.size();
nodes.push_back(Node());
nodes[idx].r = nodes.size();
nodes.push_back(Node());
}
// Lanjutkan
if (x <= mid) update(nodes[idx].l, l, mid, x, val);
if (x > mid) update(nodes[idx].r, mid+1, r, x, val);
nodes[idx].maxVal = max(nodes[nodes[idx].l].maxVal, nodes[nodes[idx].r].maxVal);
}
}
int getMax(int idx, int l, int r, int a, int b) {
if ((a <= l) && (r <= b)) {
return nodes[idx].maxVal;
} else {
int mid = (l + r) / 2;
// Jika anaknya belum dibuat, buat keduanya
if (nodes[idx].l == -1) {
nodes[idx].l = nodes.size();
nodes.push_back(Node());
nodes[idx].r = nodes.size();
nodes.push_back(Node());
}
int ret = 0;
if (a <= mid) ret = max(ret, getMax(nodes[idx].l, l, mid, a, b));
if (b > mid) ret = max(ret, getMax(nodes[idx].r, mid+1, r, a, b));
return ret;
}
}
int main() {
scanf("%d %d", &N, &M);
// Buat elemen root
nodes.push_back(Node());
for (int i = 0; i < N; i++) {
char op[5];
int x, y;
scanf("%s %d %d", op, &x, &y);
if (op[0] == 'U') {
x--;
update(0, 0, M-1, x, y);
} else {
x--;
y--;
printf("%d\n", getMax(0, 0, M-1, x, y));
}
}
}
]]></script><!-----></div><br />
<div><br class="Apple-interchange-newline" /> <hr /><h3>Penutup</h3></div><div>Pengetahuan ini mungkin tidak seberapa penting, tetapi lumayan untuk menambah ide bahwa kemalasan kadang-kadang dapat menyelesaikan masalah. Lagipula, kode yang ditulis lebih pendek dan sederhana dibandingkan menulis BST.<br />
<br />
Semoga bermanfaat!</div></div>William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-15188657284222018742018-06-18T07:57:00.002+07:002018-06-23T23:09:10.425+07:00Struktur Data Range Tree Bagian 2: RMQ<div dir="ltr" style="text-align: left;" trbidi="on"><div style="display: none;"></div>Tulisan ini adalah kelanjutan dari bagian pertama tentang <a href="http://kupaskode.blogspot.com/2017/08/range-tree.html">pengenalan range tree</a>.<br />
<br />
Kini kita mampu menghitung banyaknya elemen di suatu daerah persegi panjang. Operasi lainnya yang sering dibutuhkan adalah pencarian nilai terbesar, terkecil, atau bahkan jumlahan elemennya. Kita akan menggunakan contoh soal berikut:<br />
<br />
<i>Pada suatu lahan, terdapat N titik yang mengandung sejumlah emas. Lahan ini dapat dianggap suatu bidang 2 dimensi. Titik ke-i memiliki koordinat (xi, yi), dan memiliki kandungan emas sebanyak vi kg. Tugas kita adalah menjawab sejumlah pertanyaan berupa:</i><br />
<i><br />
</i> <i>"Pada daerah yang bermula di (x1, y1) sampai (x2, y2), berapa kandungan emas terbanyak yang ada pada suatu titik di daerah tersebut?"</i><br />
<br />
<hr /><h3>Ide Penyelesaian</h3>Dengan ide pada tulisan sebelumnya, kita dapat membagi-bagi daerah berdasarkan sumbu x. Kemudian untuk setiap suatu rentang x, kita bagi berdasarkan sumbu y.<br />
<br />
Untuk mendapatkan nilai terbesar di suatu daerah persegi panjang, lakukan query range tree seperti cara sebelumnya. Nilai terbesar sebenarnya sesederhana nilai terbesar dari tiap-tiap nilai terbesar di setiap daerah yang kita cari. Dengan struktur yang telah kita pelajari, pencarian nilai terbesar di suatu node range tree perlu dilakukan melalui linear search, sehingga kompleksitasnya O(N) dan kurang efisien.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj79j_lzRT54ARQMm5tpx1Z1VSejOeFULr1NJLjL9Kl9-8Th99koKVaFL5G35bG4fhyde7a1xgDGTJgEGPiKlLp9SLEBGQJdztzhOxly34P_tIUXsF4Nkabx-1FortC3mRlHsBoP_lRXIom/s1600/rect6872-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="768" data-original-width="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj79j_lzRT54ARQMm5tpx1Z1VSejOeFULr1NJLjL9Kl9-8Th99koKVaFL5G35bG4fhyde7a1xgDGTJgEGPiKlLp9SLEBGQJdztzhOxly34P_tIUXsF4Nkabx-1FortC3mRlHsBoP_lRXIom/s1600/rect6872-2.png" /></a></div><span id="goog_1059446111"></span><span id="goog_1059446112"></span><br />
<br />
Sebetulnya pencarian nilai terbesar di suatu node range tree ini mengingatkan kita pada masalah klasik, yaitu mencari nilai terbesar dari subbarisan di array alias RMQ (<i>Range Maximum Query</i>).<br />
<a name='more'></a><br />
<hr /><h3>Segment Tree Internal</h3>Dengan ide bahwa pencarian nilai terbesar dalam node range tree sebenarnya merupakan RMQ, kita dapat membuat segment tree di dalam node range tree itu. Pada akhirnya, mencari nilai terbesar dari subbarisan di dalam node range tree dapat dilakukan dengan query segment tree, yang kompleksitasnya O(log N).<br />
<br />
Berikut ilustrasinya. Segment tree internal pada setiap node range tree yang diperlukan ditunjukkan dengan tree berwarna coklat.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBWq04Ldt29f1Lij_QzGBFckWjz7OO0XO4Kl4r_FESVIGqDptbWZSaijUghLBQgaMcuDzUKkgMXR1FhFsfbaaY3mp4TVBhXq7A-iZ6JFSMTz6iyHQ7NQFvkfFla6ZYJE7Ygae3w9xmuO-r/s1600/rect5042.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="970" data-original-width="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBWq04Ldt29f1Lij_QzGBFckWjz7OO0XO4Kl4r_FESVIGqDptbWZSaijUghLBQgaMcuDzUKkgMXR1FhFsfbaaY3mp4TVBhXq7A-iZ6JFSMTz6iyHQ7NQFvkfFla6ZYJE7Ygae3w9xmuO-r/s1600/rect5042.png" /></a></div><br />
<br />
Data yang perlu disimpan berupa y dan nilai dari setiap titik. Setelah seluruh titik dimasukkan ke dalam range tree, barulah setiap node range tree membangun segment tree internalnya.<br />
<div><br />
</div>Segment tree internal ini perlu dikompres secara parsial. Artinya, kita akan membangun pemetaan antara nilai y yang sesungguhnya dengan bilangan 0, 1, 2, ... dst, tetapi tidak dilakukan proses unique. Hal ini bertujuan untuk memungkinkan penyimpanan data dengan y yang sama tetapi kandungan emasnya berbeda. Kita tidak boleh semata-mata menggunakan nilai terbesar saja untuk beberapa titik dengan y yang sama, sebab ketika ada operasi perubahan nilai, implementasinya menjadi rumit.<br />
<br />
Berikut implementasinya.<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int INF = 2123123123;
const int MAX_N = 131072;
class Point {
public:
int x, y, v;
bool operator<(const Point &t) const {
if (y != t.y) return y < t.y;
return v < t.v;
}
};
class RTreeNode {
public:
vector<Point> data;
vector<int> realY;
vector<int> stree;
void insert(Point p) {
data.push_back(p);
realY.push_back(p.y);
}
void build() {
sort(data.begin(), data.end());
sort(realY.begin(), realY.end());
// Bangun internal segment tree
int sz = 1;
while (sz <= data.size()) sz *= 2;
stree.resize(2*sz, 0);
buildInternalTree(0, 0, data.size()-1);
}
void buildInternalTree(int nod, int l, int r) {
if (l > r) return;
if (l == r) {
stree[nod] = data[l].v;
} else {
int mid = (l + r) / 2;
buildInternalTree(2*nod+1, l, mid);
buildInternalTree(2*nod+2, mid+1, r);
stree[nod] = max(stree[2*nod+1], stree[2*nod+2]);
}
}
int getMaxByY(int y1, int y2) {
int a = lower_bound(realY.begin(), realY.end(), y1) - realY.begin();
int b = upper_bound(realY.begin(), realY.end(), y2) - realY.begin() - 1;
if (a > b) return -INF;
return queryInternalTree(0, 0, data.size()-1, a, b);
}
int queryInternalTree(int nod, int l, int r, int a, int b) {
if ((a <= l) && (r <= b)) {
return stree[nod];
} else {
int mid = (l + r) / 2;
int ret = -INF;
if (a <= mid) ret = max(ret, queryInternalTree(2*nod+1, l, mid, a, b));
if (b > mid) ret = max(ret, queryInternalTree(2*nod+2, mid+1, r, a, b));
return ret;
}
}
};
int N, Q;
vector<point> points;
RTreeNode rtree[2*MAX_N];
vector<int> realX;
void insert(int nod, int l, int r, const Point &p) {
rtree[nod].insert(p);
if (l < r) {
int mid = (l + r) / 2;
if (p.x <= realX[mid]) {
insert(2*nod+1, l, mid, p);
} else {
insert(2*nod+2, mid+1, r, p);
}
}
}
int getMax(int nod, int l, int r, int x1, int y1, int x2, int y2) {
if ((realX[r] < x1) || (x2 < realX[l])) {
// Sepenuhnya di luar
return -INF;
} else if ((x1 <= realX[l]) && (realX[r] <= x2)) {
// Sepenuhnya di dalam
return rtree[nod].getMaxByY(y1, y2);
} else {
// Overlap
int mid = (l + r) / 2;
int ret = -INF;
if (x1 <= realX[mid]) ret = max(ret, getMax(2*nod+1, l, mid, x1, y1, x2, y2));
if (x2 > realX[mid]) ret = max(ret, getMax(2*nod+2, mid+1, r, x1, y1, x2, y2));
return ret;
}
}
int main() {
scanf("%d", &N);
for (int i = 0; i < N; i++) {
Point point;
scanf("%d %d %d", &point.x, &point.y, &point.v);
points.push_back(point);
}
// Compression
for (int i = 0; i < points.size(); i++) {
realX.push_back(points[i].x);
}
sort(realX.begin(), realX.end());
realX.erase(unique(realX.begin(), realX.end()), realX.end());
// Build
for (int i = 0; i < points.size(); i++) {
insert(0, 0, realX.size()-1, points[i]);
}
for (int i = 0; i < 2*MAX_N; i++) {
rtree[i].build();
}
// Query
scanf("%d", &Q);
for (int i = 0; i < Q; i++) {
int x1, y1, x2, y2;
scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
if (x1 > x2) swap(x1, x2);
if (y1 > y2) swap(y1, y2);
printf("%d\n", getMax(0, 0, realX.size()-1, x1, y1, x2, y2));
}
}
]]></script><!-----></div><br />
Terdapat maksimal O(log N) node range tree, dan masing-masing melakukan query pada segment tree internal. Strategi ini memberikan solusi berkompleksitas total O(log<sup>2</sup>N) per pencarian nilai terbesar di daerah persegi panjang.<br />
<br />
<hr /><h3>Operasi Update</h3><div>Segment tree internal ini juga dapat dimanfaatkan untuk mendukung operasi update. Pada contoh soal ini, anggap saja update ini berupa perubahan kandungan pada titik suatu emas.<br />
<br />
Operasi ini dilakukan dengan memperbaharui nilai segment tree internal di setiap node range tree yang mengandung titik tersebut. Hanya terdapat O(log N) node range tree yang perlu diperbaharui. Untuk setiap node, update segment tree internalnya memiliki kompleksitas O(log N). Jadi sekali update range tree kompleksitasnya O(log<sup>2</sup>N).<br />
<br />
Berikut ilustrasinya untuk proses update titik (4, 2) menjadi bernilai 5. Saya menunjukkan salah satu update pada segment tree internalnya.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj52BUFV_En8arD9bu5a6M9pRU0yfwFyh3yYi9MQuSYKXXP5oWrlxkCRrQg1HrpC2zSFyMn1RuqZLbeuSHEuD9zBExdOCwDDH19TWzY_vSjbJBaz6sU3-UN1Gu2Xi9bqA9Ze5CEpckEMKAJ/s1600/rect6872-2-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="984" data-original-width="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj52BUFV_En8arD9bu5a6M9pRU0yfwFyh3yYi9MQuSYKXXP5oWrlxkCRrQg1HrpC2zSFyMn1RuqZLbeuSHEuD9zBExdOCwDDH19TWzY_vSjbJBaz6sU3-UN1Gu2Xi9bqA9Ze5CEpckEMKAJ/s1600/rect6872-2-2.png" /></a></div><div class="separator" style="clear: both; text-align: center;"></div><br />
<br class="Apple-interchange-newline" /> Sebenarnya, ada keterbatasan dari operasi update ini. Kita tidak dapat menambah atau membuang titik. Apabila soal yang dihadapi memungkinkan dilakukan operasi offline (semua query dibaca dulu, baru dilakukan secara berurutan), maka trik yang dapat Anda gunakan adalah menganggap seluruh titik telah dimasukkan ke dalam range tree, dan memiliki nilai negatif tak berhingga. Barulah ketika titik tersebut ditambahkan secara sungguhan ke range tree, nilainya diganti menjadi nilai sesungguhnya. Cara ini menjamin titik yang belum ditambahkan secara sungguhan tidak mungkin menjadi jawaban untuk pencarian nilai terbesar.<br />
<br />
Untuk sepenuhnya mendukung penambahan atau penghapusan data, Anda perlu mengganti segment tree internal ini dengan struktur data yang mampu secara efisien menangani penambahan/pengurangan. Struktur data itu misalnya Balanced Binary Search Tree (BBST), yang salah satu implementasinya dengan <a href="http://kupaskode.blogspot.com/2013/12/treap-alternatif-balanced-binary-search.html">treap</a>.</div><div><br />
</div><hr /><h3>Generalisasi ke Dimensi Tinggi</h3><div>Kalau diperhatikan, sebenarnya range tree adalah segment tree yang mengandung segment tree. Strategi untuk membuat segment tree internal ini dapat digeneralisasi ke dimensi tiga atau lebih. Untuk dimensi 3, cukup buat node range tree terluar menampung sebuah range tree berdimensi 2, yang mana node range treenya menampung segment tree juga. </div><div><br />
</div>Untuk dimensi d, kompleksitas sekali query adalah O(log<sup>d</sup>N). Untuk dimensi yang lebih dari 2, sebenarnya O(log<sup>d</sup>N) sudah mulai lambat. Apabila Anda menemukan soal yang memerlukan pembuatan range tree dengan dimensi lebih dari 2, coba perhatikan apakah ada dimensi yang bisa dihilangkan (misalnya melalui offline processing + line sweep).<br />
<div><br class="Apple-interchange-newline" /> <hr /><h3>Latihan</h3>Sorot teks di bawah soal untuk <i>spoiler</i>.<br />
<br />
<a href="http://codeforces.com/problemset/problem/44/G">Codeforces - Shooting Gallery</a><br />
Spoiler 1: <span style="color: #353535;">Lakukan offline processing + line sweep.</span><br />
Spoiler 2: <span style="color: #353535;">Anggap setiap kotak adalah range query untuk mencari tembakan terawal yang terjadi saat itu.</span><br />
<br />
<hr /><h3>Penutup</h3></div><div>Range tree bisa dikatakan pula adalah segment tree di dalam segment tree. Hal yang mengejutkan adalah, kita tidak selalu harus berpaku pada penggunaan segment tree. Bergantung pada kebutuhan soal, kita dapat mengimplementasikan range tree dengan segment tree di dalam <a href="http://kupaskode.blogspot.com/2017/07/struktur-data-binary-indexed-tree-bit.html">BIT</a>. Bukan hanya BIT, tetapi juga struktur data lain yang dapat Anda bayangkan selama itu membantu pemrosesan data secara efisien. Saya pernah membahas ini pada tulisan tentang <a href="http://kupaskode.blogspot.com/2013/12/komposisi-struktur-data-dynamic-range.html">komposisi struktur data</a>. Sejauh ini saya pernah mengimplementasikan range tree dengan:</div><div><div><br />
</div><div>- Set di dalam segment tree (<a href="https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&category=545&page=show_problem&problem=4056">ICPC Regional Jakarta 2012 - Alien Abduction</a>)</div><div>- BST di dalam BIT (<a href="https://icpcarchive.ecs.baylor.edu/index.php?option=onlinejudge&page=show_problem&problem=3882">ICPC Regional Kuala Lumpur 2011 - Arnooks's Defense Line</a>)</div><div>- BIT di dalam BIT (<a href="https://www.spoj.com/problems/LIS2/">SPOJ - LIS2</a>)</div><div><br />
</div><div>Semoga bermanfaat!</div></div><div><br />
</div></div>William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-34101893680930588932018-06-05T13:30:00.000+07:002020-03-30T00:01:58.762+07:00Strategi Kontes ICPC<div dir="ltr" style="text-align: left;" trbidi="on">
<div style="display: none;">
</div>
Sepanjang berkompetisi ICPC, saya menyadari bahwa dinamika antar anggota tim penting untuk memaksimalkan performa tim. Maksimal di sini berarti berkompetisi sebaik mungkin, sehingga apapun hasil yang didapatkan, tidak ada kekecewaan. Tulisan ini akan membagikan pengalaman saya sepanjang 4 tahun berkompetisi di ACM ICPC. <br />
<br />
Perlu diingat bahwa tulisan ini sangat subjektif, yaitu berdasarkan pengalaman saya. Yang Anda alami mungkin berbeda, dan yang saya tuliskan hanya sebagai referensi saja.<br />
<br />
<hr />
<h3>
Komposisi Tim</h3>
Tim yang ideal adalah tim yang anggotanya saling melengkapi. Ada anggota yang mampu mengerjakan soal tipe X, ada yang mengerjakan soal tipe Y, dan sebagainya. Namun setiap anggota sebaiknya menguasai seluruh ilmu dasar.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div style="text-align: center;">
<div class="separator" style="clear: both; text-align: center;">
<a href="http://blizzardwatch.com/wp-content/uploads/2015/03/stormearthfiresamwise.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="350" data-original-width="620" height="180" src="https://blizzardwatch.com/wp-content/uploads/2015/03/stormearthfiresamwise.jpg" width="320" /></a></div>
<i>Storm, earth, </i>dan<i> fire </i>dari<i> warcraft (https://blizzardwatch.com)</i></div>
<br />
Ilmu yang setidaknya harus dimiliki setiap anggota adalah coding. Jelas bahwa setiap anggota pasti sudah bisa coding, tetapi yang saya maksud di sini adalah setiap anggota mampu menuliskan kode dengan lancar, minim <i>bug</i>, dan mudah dibaca anggota lainnya. Bila tim membebankan pekerjaan coding ke seorang anggota, maka ia kemungkinan besar akan jenuh, lelah, dan hanya berperan sebagai kuli. Tanpa berperan dalam berpikir mencari solusi, anggota ini bisa saja merasa tidak dihargai...<br />
<a name='more'></a><br />
Bila tim Anda baru dibentuk, mulailah untuk menganalisis kelebihan dan minat masing-masing. Setiap orang maksimalkan kemampuan untuk topik yang ia sukai. Untuk topik lain yang tidak seorang pun bisa atau minati, barulah dibagi sama rata. Analisis ini dapat dilakukan dengan sering-sering berlatih bersama.<br />
<br />
Menyelesaikan kontes sendiri dengan bertiga sangatlah berbeda. Terdapat faktor kerja sama, komunikasi, dan kepercayaan yang terlibat.<br />
<br />
<hr />
<h3>
Sebelum Kontes<br />
</h3>
Persiapkan semua perlengkapan selama kontes. Pastikan:<br />
<ul style="text-align: left;">
<li><i>Team notebook</i> yang dibawa tidak terlupakan atau hilang sebagian karena kesalahan panitia. Selama dibawa, lebih baik disimpan di map yang anti air (jangan seperti pada <a href="http://kupaskode.blogspot.com/2015/07/acm-icpc-world-final-2015-morocco.html">pengalaman saya</a>).</li>
<li>Tahu bagaimana cara print kode supaya <i>debugging</i> dapat dilakukan di kertas ketika komputer sedang digunakan.</li>
<li>Pastikan tidak ada kecacatan atau keanehan pada <i>hardware </i>yang diberikan. Yang selalu saya alami kalau kontes di Binus adalah <i>keyboard</i>-nya yang tidak umum, yakni tombol karakter '|' ada di pojok kiri bawah. Kalau mungkin, bawalah <i>keyboard</i> sendiri untuk menghindari kejadian ini.</li>
<li>Lacak di mana posisi WC atau tempat makan/minum, supaya ketika beristirahat tidak banyak waktu yang terbuang untuk mencari tempat-tempat tersebut.</li>
</ul>
<br />
<hr />
<h3>
Awal Kontes (00:00 - 01:30)<br />
</h3>
Umumnya, awal kontes merupakan waktu untuk menyelesaikan soal yang mudah. Soal mudah perlu segera dicari, dan diselesaikan secepatnya untuk meminimalkan penalti skor.<br />
<br />
Untuk efisiensi pencarian soal mudah, biasanya tim saya akan memecah berkas soal menjadi 3, lalu dibagi rata dan masing-masing membaca bagian mereka sendiri. Kalau ada anggota yang menemukan soal mudah, langsung dipercayakan ke anggota tersebut untuk coding. Anggota lainnya tetap membaca soal. Bila submission tidak mendapatkan AC, biarkan anggota tersebut untuk debug. Apabila masih tidak juga ditemukan, barulah anggota lainnya ikut membantu.<br />
<br />
Dengan langsung mempercayakan pengerjaan soal itu kepada anggota yang bersangkutan, kita:<br />
<ul style="text-align: left;">
<li>Mengurangi waktu yang dibutuhkan untuk menjelaskan soal, berdebat solusi, dan miskomunikasi.</li>
<li>Anggota tim lainnya dapat tetap fokus membaca soal, dan menemukan soal termudah kedua. Soal ini akan langsung di-coding begitu soal termudah AC.</li>
</ul>
Apabila anggota yang bersangkutan tidak percaya diri untuk langsung coding, mungkin saja soal itu bukan soal termudah. Apabila tidak ada anggota lain yang menemukan soal yang lebih mudah, maka silakan lakukan coding berdua.<br />
<br />
Pada akhirnya, diharapkan semua soal mudah sudah selesai dikerjakan. Kata "mudah" di sini dimaksudkan pada soal yang pada akhir kontes, hampir semua tim akan AC. Sepengalaman saya biasanya terdapat 2-3 soal seperti ini.<br />
<br />
<br />
<hr />
<h3>
Pertengahan Kontes (01:30 - 03:30)</h3>
<h3>
</h3>
<h4 style="text-align: left;">
Strategi Pengerjaan </h4>
Ini adalah waktunya untuk mengerjakan soal-soal yang lebih sulit (medium). Hal yang penting untuk dipastikan adalah:<br />
<ol style="text-align: left;">
<li>Setiap anggota tim tahu <u>dan mengerti</u> setiap soal yang ada. Entah sudah berapa banyak kontes kurang maksimal yang saya alami karena saya tidak tahu ada soal yang bisa saya kerjakan.</li>
<li>Untuk setiap soal, ada baiknya dibaca oleh setidaknya 2 anggota tim. Hal ini diperlukan untuk mengurangi peluang salah mengerti soal. Entah sudah berapa banyak kontes kurang maksimal yang saya alami karena saya salah mengerti soal, dan teman lainnya salah mengerti soal tersebut dari deskripsi saya (padahal soal tersebut sangat mungkin untuk dikerjakan dan AC).</li>
</ol>
Luangkanlah beberapa menit untuk memastikan kedua hal di atas terpenuhi. Waktu 15 menit yang digunakan untuk memahami semua soal tersisa sangat sepadan dengan menghilangkan peluang adanya "aduuuh... harusnya tadi baca soal ini..." pada akhir kontes.<br />
<br />
Mengenai pengerjaan, soal yang dihadapi kali ini mungkin butuh diskusi dan analisis bersama. Tariklah salah satu teman untuk berdiskusi, terutama bila Anda tahu bahwa ia sekiranya memiliki pengetahuan tentang algoritma atau struktur data yang diperlukan. Seringnya berlatih bersama akan meningkatkan pengetahuan Anda tentang apa yang teman Anda ketahui.<br />
<br />
--<br />
<h4 style="text-align: left;">
Istirahat</h4>
<div style="text-align: left;">
Semakin lama seseorang duduk-duduk, berpikir, dan coding, semakin cepat orang itu untuk jenuh. Apalagi kalau itu dilakukan selama 2 jam. Tidak perlu ragu apabila selama periode ini Anda butuh istirahat. Misalnya keluar untuk ke toilet, makan sesuatu, atau kombinasi dari keduanya. Meluangkan 5 menit untuk istirahat merupakan pengeluaran yang sepadan juga untuk menjaga stamina selama kompetisi. Sebagai bonus, kadang-kadang saat berjalan ke toilet Anda bisa mendapatkan inspirasi. Saya pernah beberapa kali mendapatkan ide saat melakukan hal tersebut.</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
Tapi tentu saja jangan beristirahat terlalu sering. Anggota tim lainnya sedang bekerja keras, dan butuh partisipasi Anda.</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
-- </div>
<h4>
Moral </h4>
Pada bagian ini, saya juga ingin memperkenalkan sebuah konsep yang baru saya pahami setelah beberapa kali kontes tim, yaitu moral.<br />
<br />
Moral ini bukan nilai baik atau buruk yang diambil pada akhir suatu cerita. Moral yang saya maksud berasal dari kata <i>morale </i>pada Bahasa Inggris, yang artinya seperti antusiasme dalam mengerjakan sesuatu. Memang kebetulan kata <i>moral </i>dan <i>morale </i>pada Bahasa Inggris diserap ke Bahasa Indonesia menjadi kata yang ejaannya sama, yaitu moral.<br />
<br />
Sebagai contoh, mengapa dalam kegiatan berkelompok kadang-kadang kita diminta membuat yel-yel? Apakah untuk lucu-lucuan? Mungkin, dan menurut saya tujuan utamanya adalah untuk membuat tim menjadi kompak dan antusias dalam mencapai tujuan. Ketika ada anggota tim yang lemas, mulai kurang bersemangat, atau kehilangan motivasi, lakukanlah yel-yel bersama. Yel-yel ini akan meningkatkan moral tim.<br />
<br />
Contoh lainnya adalah pada <i>warcry, </i>yaitu kata-kata yang diucapkan saat peperangan, misalnya "merdeka atau mati!<i>"</i>. Pengucapan kata-kata ini akan membangkitkan semangat prajurit untuk ingat dengan tujuan mereka dan terus bertempur. Pengucapan <i>warcry </i>akan meningkatkan moral prajurit.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4prgy7CEQmxt5HeOmzgpgXNZ0ox4UBOKTs2eofm7jvbBYOLZi3MRsRZsbbjcKj86DF3QdxDQmYR2ncooOhJReWhAiFLJAkP_sJBi7fuSYdEgnb_CsGjuCsbJtrOoaOraskmXbFs8uG7de/s1600/tonight-we-dine-in-hell-thumb.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="161" data-original-width="305" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4prgy7CEQmxt5HeOmzgpgXNZ0ox4UBOKTs2eofm7jvbBYOLZi3MRsRZsbbjcKj86DF3QdxDQmYR2ncooOhJReWhAiFLJAkP_sJBi7fuSYdEgnb_CsGjuCsbJtrOoaOraskmXbFs8uG7de/s1600/tonight-we-dine-in-hell-thumb.jpg" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
Apabila <i>warcry</i> diucapkan dengan wajah seperti ini, semangat berperang akan membara.</div>
<div style="text-align: center;">
</div>
<br />
Kembali ke urusan ICPC. Moral tim sangat penting selama kompetisi berlangsung. Lima jam berkompetisi adalah waktu yang panjang. Mulai putus asa ketika terus menerus mendapatkan WA pada soal tingkat menengah adalah hal yang sering saya alami. Setiap kali mendapatkan WA lagi pada suatu soal, semakin tinggi tingkat frustrasi saya. Lalu apa yang terjadi? Bisa saja saya menjadi tidak semangat lagi untuk meneruskan kompetisi dan menjadi "<i>stayer</i>" kalau di istilah battlenet; alias pemain yang tidak lagi berjuang dan pasrah menunggu kekalahan.<br />
<br />
Namun, ada efek yang lebih parah dari kehilangannya semangat salah satu anggota tim. Efek ini adalah hilangnya semangat dari seluruh anggota tim. Ketika seseorang melihat teman seperjuangannya tidak lagi semangat, mungkin saja ia menjadi kecewa, sedih, ikut putus asa, atau bahkan kesal. Moral yang ada pada seluruh anggota tim hilang. Efek perasaan negatif ini akan menghancurkan performa tim pada kompetisi.<br />
<br />
Sejauh ini, saya selalu merasakan penurunan moral ketika mendapatkan WA terus menerus. Memang tidak langsung menjatuhkan, dan dalam tim kami selalu berkata "ayo-ayo, dicoba lagi, dicari mana yang salah". Namun apa dayanya apabila WA terus berlangsung, dan tidak ada soal AC selama 2 jam?<br />
<br />
Pengaruh sebaliknya saya rasakan ketika mendapatkan AC. Setiap AC yang didapatkan akan meningkatkan moral tim. Anggota tim menjadi senang dan bersemangat untuk mencari solusi untuk soal lainnya. Rasa percaya diri meningkat. Seluruh perasaan positif ini sangat membantu tim dalam mencapai performa terbaik.<br />
<br />
Lalu bagaimana caranya untuk menjaga moral tim tetap pada keadaan yang sehat? Sepengalaman saya, yang bisa dilakukan adalah:<br />
<ul style="text-align: left;">
<li>Apabila sudah mendapatkan 3x WA berturut-turut pada suatu soal, coba ganti soal lain untuk dikerjakan. Cetak kodenya, lalu coba <i>debug</i> di kertas, sementara teman lain mulai coding untuk soal yang dapat ia kerjakan.</li>
<li>Saat <i>debugging </i>di kertas dan menemukan 1 <i>bug</i>, tidak boleh terburu-buru dan langsung mau memperbaikinya di komputer dan submit. Diharuskan untuk membaca seluruh kode dan mendeteksi adanya bug sebanyak mungkin. Sepengalaman saya, perbaikan dapat dilakukan kalau komputer sudah tidak digunakan anggota tim lainnya. Hal ini juga menjaga konsentrasi anggota tim lainnya tidak terganggu saat coding.</li>
<li>Kurangi mengekspresikan frustrasi kepada anggota tim lainnya. Misalnya menghela nafas, cemberut, atau mengucapkan nama hewan. <i>Effect: -1 morale to nearby allies. </i></li>
<li>(ini saya tidak pernah) Ciptakan sebuah yel-yel atau <i>warcry</i> untuk tim Anda. Secara samar-samar saya ingat ada tim lain yang menggunakan strategi ini.</li>
</ul>
Efek moral ini baru saya rasakan sejak suatu kontes, yaitu ITBPC 2012. Tim kami mendapatkan WA terus menerus untuk sejumlah soal yang kami kerjakan. Hal ini berlangsung selama 2 jam, dan kami sudah sangat frustrasi. Pada akhirnya, didapatkan 1 soal yang AC. Tiba-tiba kami menjadi semangat lagi dan secara ajaib 1 demi 1 soal lainnya menjadi AC juga. Dengan pengalaman inilah saya merasa betapa pentingnya untuk menjaga moral tim.<br />
<br />
<hr />
<h3>
Akhir Kontes (03:30 - 05:00)<br />
</h3>
Menjelang akhir kontes, sangat mungkin tersisa sejumlah soal dengan tingkat kesulitan menengah, dan diperkirakan soal-soal tersebut dapat AC. Teruslah mengejar AC untuk soal-soal tersebut. Kalau sudah tidak ada, berarti sudah saatnya Anda menghadapi soal-soal maut.<br />
<br />
Terdapat sebuah filosofi komposisi soal yang saya pelajari pada simposium ICPC World Final 2015. Soal-soal pada kontes akan dirancang sedemikian sehingga:<br />
<ol style="text-align: left;">
<li>Setiap soal dapat diselesaikan oleh setidaknya salah satu tim.</li>
<li>Tidak ada tim yang menyelesaikan semua soal.</li>
<li>Setiap tim menyelesaikan setidaknya satu soal.</li>
</ol>
Butiran (1) dan (2) memberikan makna bahwa Anda perlu memilih soal sulit mana yang hendak diinvestasikan. Soal sulit membutuhkan investasi waktu dan tenaga yang besar. Sekalinya Anda berhasil menyelesaikan soal itu, kemungkinan waktu yang tersisa terlalu sedikit untuk mengerjakan soal sulit lainnya.<br />
<br />
Bukannya pesimis, tetapi ini yang biasanya saya alami. Tim kami akan memilih soal yang kira-kira dapat diselesaikan, berdasarkan pengalaman dan jenis soal yang ada. Bila tim tidak punya riwayat yang bagus dalam menyelesaikan soal berjenis<i> </i>algoritma graph yang kompleks, cobalah untuk hindari menghabiskan waktu pada soal tersebut. Cari, dan gunakan waktu untuk soal yang paling mungkin diselesaikan.<br />
<br />
Sebagai tambahan, tim yang mampu menyelesaikan soal sulit inilah yang biasanya memenangkan kompetisi. Soal seperti ini biasa disebut <i>decider problem</i>. Dalam suatu kompetisi, bisa saja terdapat 2-3 <i>decider problem.</i><br />
<br />
Keletihan yang dirasakan menjelang akhir kontes biasanya cukup tinggi. Dukunglah anggota tim yang sedang bekerja, misalnya memperhatikan anggota tim yang sedang coding. Ketika seseorang yang sudah kelelahan dibantu untuk coding, ia akan lebih termotivasi untuk terus bekerja. Apabila orang yang sedang menulis kode sudah merasa percaya diri, dan kode program yang ditulis ternyata sederhana, segeralah beralih ke anggota tim yang satunya untuk mendiskusikan soal lainnya. Waktu tersisa yang sedikit bukan alasan untuk langsung menyerah dan bersantai.<br />
<br />
Jagalah terus moral tim! Apabila pada pertengahan kontes tim Anda berhasil menjaga moral yang baik, maka bagian akhir kontes umumnya dapat terjaga pula. Hasil kontes pada akhirnya akan maksimal.<br />
<br />
<hr />
<h3>
Kontes Berakhir<br />
</h3>
Berterimakasihlah pada rekan tim atas kerja sama yang telah berlangsung selama kompetisi. Apabila kerja sama tim yang terlaksana baik, maka pada kontes berikutnya tim Anda akan lebih kokoh lagi.<br />
<br />
Apabila ternyata kontes yang berlangsung kurang maksimal, boleh saja dilakukan sesi evaluasi untuk mengetahui apa saja kekurangan selama kontes berlangsung.<br />
<br />
Jangan lupa untuk bersantai. Biasanya saya langsung makan yang banyak, menjarah hidangan yang diberikan. <br />
<br />
<hr />
<h3>
Penutup</h3>
Kontes ICPC mirip seperti olah raga berkelompok. Strategi dan kerja sama merupakan unsur yang penting dalam menjamin tim bekerja secara maksimal.<br />
<br /></div>
William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-31355410061807995012018-04-14T11:40:00.001+07:002018-06-19T12:23:24.675+07:00Buku Pemrograman Kompetitif Dasar Dirilis!<div dir="ltr" style="text-align: left;" trbidi="on">
Seperti yang sudah saya sempat bocorkan <a href="http://kupaskode.blogspot.fr/2018/01/riset-belajar-osn-komputer.html">sebelumnya</a>, saya dan teman-teman dari IA TOKI mempersiapkan materi pembelajaran yang baru.<br />
<br />
Materi ini adalah buku yang berjudul "Pemrograman Kompetitif Dasar", atau disingkat PKD.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLKL6gRKnjlaF9AWZx649w6WkC-83L25P9rOrYuRoKjwIayHV-sUwR2ySGdHw6ZTu0nnOwZJDbLIOR09i-WSU-u2wiphY2UxCB85ELnc_XXKwDRXOarL_jyS4Ru0KPlqaCGAHfo4lsV_fO/s1600/pkd.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1076" data-original-width="774" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLKL6gRKnjlaF9AWZx649w6WkC-83L25P9rOrYuRoKjwIayHV-sUwR2ySGdHw6ZTu0nnOwZJDbLIOR09i-WSU-u2wiphY2UxCB85ELnc_XXKwDRXOarL_jyS4Ru0KPlqaCGAHfo4lsV_fO/s640/pkd.png" width="221" /></a></div>
<br />
<br />
PKD ini dibuat dengan bahan dasar berupa slide-slide materi di TLX Training Gate yang sebelumnya telah ditulis. Slide-slide ini tidak hanya sekedar dibukukan, sebab banyak bagian yang sebelumnya sulit dipaparkan lewat slide kini dijelaskan lebih mendalam lewat narasi. Terdapat pula 2 bab tambahan, yaitu struktur data nonlinear dan algoritma graf.<br />
<br />
<div style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcjRrEksmavWyfXq5vmNrqJt8vHP1EwRpXd4hmPaFo0yaK-571vsQRIUNvgMTU36EtvISpaSJqsvqSu8AuCZ2R4wId_9qh0_z81ggAuiB6046vIyY0hKgSu46nK7mRLfddCvfeWp7ca_z8/s1600/Screenshot+from+2018-03-17+20%253A44%253A38.png" imageanchor="1"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcjRrEksmavWyfXq5vmNrqJt8vHP1EwRpXd4hmPaFo0yaK-571vsQRIUNvgMTU36EtvISpaSJqsvqSu8AuCZ2R4wId_9qh0_z81ggAuiB6046vIyY0hKgSu46nK7mRLfddCvfeWp7ca_z8/s500/Screenshot+from+2018-03-17+20%253A44%253A38.png" width="500" /> </a></div>
<div style="text-align: center;">
Tampilan dalam PKD</div>
<br />
<br />
Buku ini sekarang tersedia dalam bentuk elektronik, Anda dapat mengunduhnya secara gratis di link pada akhir tulisan. Apakah akan ada versi cetaknya? Saya belum tahu. Untuk hal itu mungkin perlu menanti perkembangan yang lebih lanjut, berhubung mencetak dan mendistribusikan buku terlihat seperti pekerjaan yang tidak mudah.<br />
<br />
Bagi kalian yang haus akan ilmu dan sedang dalam persiapan OSN komputer, silakan membaca buku ini dan jangan lupa untuk disebarluaskan kepada siapapun yang juga membutuhkan buku ini. Semoga bermanfaat!<br />
<h3 style="text-align: center;">
<a href="https://toki.id/buku-pemrograman-kompetitif-dasar" target="_blank"><span style="font-size: large;">[baca selengkapnya di sini]</span></a></h3>
</div>
William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-25755592100665286632018-01-23T04:37:00.000+07:002018-04-14T11:41:57.260+07:00Riset Belajar OSN Komputer<div dir="ltr" style="text-align: left;" trbidi="on">
<div style="display: none;">
</div>
Beberapa bulan yang lalu, saya sempat kembali aktif menulis. Namun kemudian saya kembali menghilang. Sebenarnya selama saya menghilang, banyak yang saya kerjakan berkaitan dengan <i>competitive programming</i>.<br />
<br />
Jadi sekitar bulan September, saya menjadi panitia Gemastik 2017 bagian lomba pemrograman, yang kala itu tuan rumahnya di Universitas Indonesia. Tahun 2016 juga tuan rumahnya UI, tetapi saya sedang mengerjakan proyek dan sibuk membantu seleksi tim inti ACM-ICPC untuk UI. Jadilah saya tidak membantu. Tahun 2017, berhubung saya sudah lebih luang dan sekaligus balas budi kepada UI, saya ikutlah menjadi panitia.<br />
<br />
Setelah Gemastik berakhir, saya diajak Aji yang tiba-tiba mau mengurus konten untuk TOKI. Sejauh ini konten TOKI yang saya kerjakan adalah materi TLX Training Gate, yang sudah dimulai sejak 2014. Aji mengajak saya untuk melakukan riset apakah sebenarnya materi dan soal latihan TLX Training Gate ini benar-benar bermanfaat. Sebelumnya saya tidak pernah terpikirkan tentang aspek ini, jadi hanya sekedar menulis saja.<br />
<br />
Setelah mendapat hasil kuesioner di grup Facebook Olimpiade Informatika Indonesia, kami menyimpulkan bahwa masih banyak orang yang tidak tahu tentang pembelajaran pemrograman kompetitif di Indonesia. Solusinya adalah dengan membuat 1 halaman situs berisi kumpulan informasi pembelajaran yang singkat, padat, jelas, dan sistematis.<br />
<br />
Bersama-sama dengan Mamat dan Fairuzi, kami akhirnya membuat:<br />
<br />
<div style="text-align: center;">
<span style="font-size: large;"><b><a href="https://osn.toki.id/">https://osn.toki.id/</a></b></span></div>
<br />
Seperti yang telah dijelaskan, situs ini adalah 1 halaman berisi kumpulan informasi untuk mulai belajar menuju OSN komputer. Belajar ini maksudnya mulai dari OSK, OSP, sampai OSN komputer. Yang terpenting adalah tulisannya disusun dengan sistematis, jadi tidak bingung dengan sumber pembelajaran yang saat ini berceceran. Seluruh sumber pembelajaran yang dicantumkan dapat diakses secara gratis.<br />
<br />
Selain pembuatan situs itu, kami juga menyiapkan konten baru untuk pembelajaran OSN. Konten ini disesuaikan juga dengan hasil riset sebelumnya. Informasi lebih lanjut akan saya berikan ketika konten ini sudah selesai.<br />
<br />
Selamat memulai belajar menuju OSN komputer!</div>
William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-25204482653111521392017-08-21T04:10:00.000+07:002018-06-18T10:07:27.921+07:00Struktur Data Range Tree<div dir="ltr" style="text-align: left;" trbidi="on"><div style="display: none;"></div>Terdapat permintaan untuk menuliskan tentang range tree, jadi kali ini saya akan membahasnya dari awal.<br />
Anda perlu diharapkan memiliki pengetahuan tentang apa itu segment tree terlebih dahulu.<br />
<br />
Range tree yang saya tulis ini mungkin berbeda dengan range tree pada ilmu komputer pada umumnya. Saya akan membahas untuk spesifik range tree yang sering digunakan pada kompetisi pemrograman.<br />
<br />
<hr /><h3>Tentang Range Tree</h3>Range tree adalah struktur data yang dapat melayani operasi-operasi pada suatu daerah berbentuk persegi panjang secara efisien. Untuk contoh pembahasan ini, kita akan menggunakan contoh soal berikut.<br />
<br />
Pada suatu lahan, terdapat N titik yang mengandung sejumlah emas. Lahan ini dapat dianggap suatu bidang 2 dimensi. Titik ke-i memiliki koordinat (xi, yi). Tugas kita adalah menjawab sejumlah pertanyaan berupa:<br />
<br />
"Pada daerah yang bermula di (x1, y1) sampai (x2, y2), ada berapa titik emasnya?"<br />
<br />
Perhatikan gambar berikut.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbg280YIeTL8RjawuzQ5-PUMaArivJ9RDjMLVoAgpI3waaAHMIi1YiJ2hVAC1O-uEwMO7RfQYZ2Jdb0Yt_wNxY-zdhDhZVWVno03rGBGFtW4elVff9jASvIWiPNu37XRrvEIGFRLF6n2Pk/s1600/g4745.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="549" data-original-width="550" height="319" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbg280YIeTL8RjawuzQ5-PUMaArivJ9RDjMLVoAgpI3waaAHMIi1YiJ2hVAC1O-uEwMO7RfQYZ2Jdb0Yt_wNxY-zdhDhZVWVno03rGBGFtW4elVff9jASvIWiPNu37XRrvEIGFRLF6n2Pk/s320/g4745.png" width="320" /></a></div><br />
Misalkan bulatan pada koordinat adalah titik yang mengandung sejumlah emas. Jawaban untuk banyaknya titik emas pada daerah yang bermula di (2, 2) sampai (8, 7) adalah 5.<br />
<a name='more'></a><br />
<hr /><h3>Konstruksi Range Tree</h3>Range tree sangat mirip dengan segment tree. Perbedaannya adalah setiap node pada range tree tidak menyimpan nilai agregat, melainkan data yang tercakup dalam range-nya.<br />
<br />
Misalnya kita memiliki data koordinat sebagai berikut:<br />
<pre>x: 1 2 4 5 6 7 8 9
y: 3 1 4 2 5 9 7 8</pre>Untuk membuat range tree, kita akan membagi-bagi data tersebut ke dalam struktur yang mirip dengan segment tree, berdasarkan nilai sumbu x.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh58OufuKOw2AODVdZJEf29h7S7tXFToZkxmwMLrxNqdCK1ElKw8Ig8Xl2VRHm1rWmQhMgjn4654c07G7zHzpzye2fijgu6y27XS0GetaJhofedD-2lrMfKTYywJD8UqxR-Qbkpx8jwgCRA/s1600/g7107.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="308" data-original-width="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh58OufuKOw2AODVdZJEf29h7S7tXFToZkxmwMLrxNqdCK1ElKw8Ig8Xl2VRHm1rWmQhMgjn4654c07G7zHzpzye2fijgu6y27XS0GetaJhofedD-2lrMfKTYywJD8UqxR-Qbkpx8jwgCRA/s1600/g7107.png" /></a></div><br />
Kemudian urutkan setiap segmen berdasarkan nilai sumbu y. Hal ini mengakibatkan nilai sumbu-x pada setiap node berantakan, tetapi ini bukan masalah.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVnYGWcv4I6nfp5wJy5TqD39tRsiqUmauUImKCf54hx5pV18krc9JCRjqTluKoHechD265s8b9LvaCHGJBindWKa6cEVtru_e92uelOdCCMc5oZeiYk2PUCPuKLLHIPuxqW_HfBTeC6J-V/s1600/g7034.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="308" data-original-width="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVnYGWcv4I6nfp5wJy5TqD39tRsiqUmauUImKCf54hx5pV18krc9JCRjqTluKoHechD265s8b9LvaCHGJBindWKa6cEVtru_e92uelOdCCMc5oZeiYk2PUCPuKLLHIPuxqW_HfBTeC6J-V/s1600/g7034.png" /></a></div><br />
Selesailah pembuatan range tree. Konsepnya sangat sederhana.<br />
<br />
Kompleksitas untuk membuat struktur yang mirip dengan segment tree adalah O(N log N).<br />
Untuk setiap tingkat, total kompleksitas untuk mengurutkannya adalah O(N log N). Karena terdapat O(log N) ketinggian, maka kompleksitas pembangunan range tree adalah O(N log<sup>2</sup> N).<br />
<br />
Terdapat caranya supaya pembangunan range tree hanya O(N log N). Namun itu adalah cerita untuk lain hari.<br />
<br />
Untuk memori, perhatikan bahwa tingkat pada range tree memiliki N elemen. Kembali lagi karena ketinggian dari tree adalah O(log N), didapatkan bahwa penggunaan memori range tree untuk N elemen adalah O(N log N).<br />
<br />
<hr /><h3>Menghitung Titik dalam Persegi Panjang</h3>Operasi menghitung banyaknya titik dalam (x1, y1) .. (x2, y2) dilakukan dengan mudah. Mulai penelusuran dari root range tree.<br />
<br />
Untuk setiap penelusuran pada suatu node, terdapat 3 kasus:<br />
<ol style="text-align: left;"><li>Node ini merepresentasikan segmen yang tidak bersentuhan dengan [x1, x2] pada sumbu x. Untuk kasus ini, hentikan penelusuran.</li>
<li>Node ini merepresentasikan segmen yang bersentuhan [x1, x2] pada sumbu x, tetapi tidak berada di dalam [x1, x2]. Untuk kasus ini, telusuri kedua anak node ini.</li>
<li>Kasus yang tersisa adalah node ini merepresentasikan segmen yang sepenuhnya berada di dalam [x1, x2] pada sumbu x. Untuk kasus ini, hitung banyaknya titik yang berada di dalam [y1, y2] pada sumbu y <b>menggunakan binary search</b>.</li>
</ol><br />
Perhatikan contoh berikut untuk mencari banyaknya titik dalam (2, 2) .. (8, 7). Warna merah menyatakan node yang memenuhi kasus ke-3, dan warna biru menyatakan data yang ditemukan dengan binary search. Ingat bahwa prosesnya adalah:<br />
<ol style="text-align: left;"><li>Telusuri node-node (tree traversal) seperti pada segment tree.</li>
<li>Untuk setiap node yang mencakup data bernilai sumbu-x di dalam [x1, x2], lakukan binary search.</li>
</ol><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioyNs94fU3NrHOnR3WNdDfMXD7fM3A48Gy6VEQzozw3HQI-hfYYLhbsNiImHfYufni63xe4VhKMrJgKaxFFwD1m2Vl-2yOSsSBbKTqau3PTlzJs65OnydnwArPhoQ9tYM0DbGsPfv4ngOo/s1600/g6888.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="710" data-original-width="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioyNs94fU3NrHOnR3WNdDfMXD7fM3A48Gy6VEQzozw3HQI-hfYYLhbsNiImHfYufni63xe4VhKMrJgKaxFFwD1m2Vl-2yOSsSBbKTqau3PTlzJs65OnydnwArPhoQ9tYM0DbGsPfv4ngOo/s1600/g6888.png" /></a></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitWKQwYC-edSLjWrSGlAkjCnLPnRLr5Tx7O0zRJspN8Qg7q3B96mI5ySQMofdATlt-UOz2LZsFgqujxGIEbRxcFQIVdEsKxtfp810Zxf8tS9De31Z1NYKaNm6VjpQvDkQrIXCY1M30DaE7/s1600/g6888.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><br />
</a></div><br />
Setelah bagian memilah-milah segmen berdasarkan sumbu x, dijamin terdapat O(log N) segmen yang dipilih (seperti pada segment tree). Kemudian untuk setiap segmen dipilih, dilakukan binary search O(log N) pada titik-titik di dalamnya berdasarkan sumbu y. Sehingga kompleksitas untuk sekali menghitung banyaknya titik dalam daerah persegi panjang adalah O(log<sup>2</sup> N).<br />
<br />
<hr /><h3>Implementasi Range Tree</h3>Pada ilmu pemrograman, terdapat konsep bernama "encapsulation". Konsep ini kurang lebih menyatakan bahwa:<br />
<br />
"Pecah program besar menjadi beberapa komponen lebih kecil yang saling berinteraksi. Setiap komponen tidak perlu tahu implementasi komponen lainnya secara mendalam. Bila hal-hal tersebut dipenuhi, maka sakit kepala dapat dihindari".<br />
<br />
Contoh untuk soal ini, ketika kita sedang berurusan dengan komponen x, kita tidak perlu peduli apa yang terjadi pada komponen y. Demikian juga kita tidak perlu peduli dengan nilai sumbu x ketika melakukan binary search. Hal ini membantu otak kita untuk fokus pada setiap komponen, ketimbang membuat program besar yang langsung mempertimbangkan seluruh komponen, yang mungkin mengakibatkan sakit kepala muncul.<br />
<br />
Dalam rangka mendukung konsep "encapsulation", saya menganjurkan untuk mengimplementasikan node range tree menggunakan class atau struct pada C++, atau hal serupa pada bahasa pemrograman Anda. Saya akan menggunakan class pada C++ untuk tulisan ini.<br />
<br />
Untuk keperluan implementasi, asumsikan kita memiliki struktur berikut:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
class Point {
public:
int x, y;
};
]]></script><!-----></div><br />
Untuk node range tree, definisikan sebuah class yang menyimpan vector untuk menampung semua nilai y pada suatu node.<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
class RTreeNode {
public:
vector<int> y;
};
]]></script><!-----></div><br />
Kemudian definisikan beberapa variabel global:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
int N; // Banyaknya titik
vector<point> points; // Titik-titik pada soal
RTreeNode rtree[MAX_SIZE]; // Node-node pada range tree
]]></script><!-----></div><br />
Untuk tahap pertama, tampung dulu semua kemungkinan koordinat yang ada, kemudian <a href="http://kupaskode.blogspot.com/2017/08/grid-compression.html">lakukan kompresi</a> pada sumbu-x.<br />
Kompresi ini sebenarnya sesederhana mengurutkan seluruh sumbu x yang mungkin, lalu membuang nilai yang tidak unik. Hal ini akan mempermudah kita dalam membuat struktur range tree yang seimbang, hemat memori, dan bebas sakit kepala karena elemen yang tidak unik.<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
vector<int> realX; // Definisikan sebagai variabel global
...
// Dalam program utama
for (int i = 0; i < points.size(); i++) {
realX.push_back(points[i].x);
}
sort(realX.begin(), realX.end());
realX.erase(unique(realX.begin(), realX.end()), realX.end());
]]></script><!-----></div><br />
Setelah tahap kompresi, kita dapat memulai bagian membuat range tree. Definisikan fungsi untuk memasukkan data ke dalam range tree:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
class RTreeNode {
public:
...
// Tambahkan bagian insert
void insert(int v) {
y.push_back(v);
}
};
...
void insert(int nod, int l, int r, const Point &p) {
rtree[nod].insert(p.y);
if (l < r) {
int mid = (l + r) / 2;
if (p.x <= realX[mid]) {
insert(2*nod+1, l, mid, p);
} else {
insert(2*nod+2, mid+1, r, p);
}
}
}
]]></script><!-----></div><br />
Selebihnya cukup masukkan semua data ke dalam range tree, dan urutkan tiap segmen.<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
class RTreeNode {
public:
...
void sort() {
std::sort(y.begin(), y.end());
}
};
...
// Pada program utama
for (int i = 0; i < points.size(); i++) {
insert(0, 0, realX.size()-1, points[i]);
}
for (int i = 0; i < 2*MAX_N; i++) {
rtree[i].sort();
}
]]></script><!-----></div><br />
Bagian menjawab pertanyaan, ikuti pembagian kasus yang telah dijelaskan pada bagian sebelumnya. <br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
class RTreeNode {
public:
...
int getCountByY(int y1, int y2) {
return upper_bound(y.begin(), y.end(), y2) - lower_bound(y.begin(), y.end(), y1);
}
};
...
int getCount(int nod, int l, int r, int x1, int y1, int x2, int y2) {
if ((realX[r] < x1) || (x2 < realX[l])) {
// Sepenuhnya di luar
return 0;
} else if ((x1 <= realX[l]) && (realX[r] <= x2)) {
// Sepenuhnya di dalam
return rtree[nod].getCountByY(y1, y2);
} else {
// Overlap
int mid = (l + r) / 2;
int result = 0;
if (x1 <= realX[mid]) result += getCount(2*nod+1, l, mid, x1, y1, x2, y2);
if (x2 > realX[mid]) result += getCount(2*nod+2, mid+1, r, x1, y1, x2, y2);
return result;
}
}
]]></script><!-----></div><br />
Perhatikan bahwa fungsi getCount didefinisikan dalam class RTreeNode. Dengan konsep encapsulation, kita membuat kode lebih mudah dibaca dan modular. Pada C++, Anda dapat menggunakan fungsi lower_bound dan upper_bound dari include algorithm:<br />
<ul style="text-align: left;"><li>lower_bound(arr.begin(), arr.end(), x): mengembalikan iterator pada arr yang menunjuk ke elemen pertama yang >= x</li>
<li>upper_bound(arr.begin(), arr.end(), x): mengembalikan iterator pada arr yang menunjuk ke elemen pertama yang > x</li>
</ul>Hafalkan kegunaan kedua fungsi tersebut, karena dapat menghemat penulisan binary search. Jangan sampai tertukar!<br />
<br />
Selesailah implementasi range tree untuk soal ini. Berikut kode lengkapnya:<br />
<div><script class="brush: cpp" type="syntaxhighlighter"><![CDATA[
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
class Point {
public:
int x, y;
};
class RTreeNode {
public:
vector<int> y;
void insert(int v) {
y.push_back(v);
}
void sort() {
std::sort(y.begin(), y.end());
}
int getCountByY(int y1, int y2) {
return upper_bound(y.begin(), y.end(), y2) - lower_bound(y.begin(), y.end(), y1);
}
};
const int MAX_N = 131072;
int N, Q;
vector<point> points;
RTreeNode rtree[2*MAX_N];
vector<int> realX;
void insert(int nod, int l, int r, const Point &p) {
rtree[nod].insert(p.y);
if (l < r) {
int mid = (l + r) / 2;
if (p.x <= realX[mid]) {
insert(2*nod+1, l, mid, p);
} else {
insert(2*nod+2, mid+1, r, p);
}
}
}
int getCount(int nod, int l, int r, int x1, int y1, int x2, int y2) {
if ((realX[r] < x1) || (x2 < realX[l])) {
// Sepenuhnya di luar
return 0;
} else if ((x1 <= realX[l]) && (realX[r] <= x2)) {
// Sepenuhnya di dalam
return rtree[nod].getCountByY(y1, y2);
} else {
// Overlap
int mid = (l + r) / 2;
int result = 0;
if (x1 <= realX[mid]) result += getCount(2*nod+1, l, mid, x1, y1, x2, y2);
if (x2 > realX[mid]) result += getCount(2*nod+2, mid+1, r, x1, y1, x2, y2);
return result;
}
}
int main() {
scanf("%d", &N);
for (int i = 0; i < N; i++) {
Point point;
scanf("%d %d", &point.x, &point.y);
points.push_back(point);
}
// Compression
for (int i = 0; i < points.size(); i++) {
realX.push_back(points[i].x);
}
sort(realX.begin(), realX.end());
realX.erase(unique(realX.begin(), realX.end()), realX.end());
// Build
for (int i = 0; i < points.size(); i++) {
insert(0, 0, realX.size()-1, points[i]);
}
for (int i = 0; i < 2*MAX_N; i++) {
rtree[i].sort();
}
// Query
scanf("%d", &Q);
for (int i = 0; i < Q; i++) {
int x1, y1, x2, y2;
scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
if (x1 > x2) swap(x1, x2);
if (y1 > y2) swap(y1, y2);
printf("%d\n", getCount(0, 0, realX.size()-1, x1, y1, x2, y2));
}
}
]]></script><!-----></div><br />
<h4>Catatan Implementasi</h4>Pada penjelasan dan gambar-gambar yang saya berikan, tidak ada dua titik dengan nilai sumbu-x yang sama. Ini sekedar untuk mempermudah penjelasan.<br />
<br />
Apabila terdapat beberapa titik dengan nilai sumbu-x yang sama, cukup ikuti algoritma yang sama. Artinya, setiap node pada range tree yang memiliki kedalaman sama mungkin memiliki banyaknya elemen berbeda. Meskipun terlihat "berat sebelah", hal ini tidak menjadi masalah, sebab operasi di dalam node tersebut adalah binary search O(log N) yang sangat cepat. Kompleksitas akhir selalu O(log<sup>2</sup> N) untuk operasi pencarian.<br />
<br />
Gambar berikut menunjukkan range tree dengan beberapa titik dengan komponen x dan y yang sama. Untuk kejelasan, perhatikan informasi tentang rentang x setiap node pada tulisan merah.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_yeDy9VPfrmKUrRBH_HrXE8Ei7-SFudIKeAFv5FpBb10We5y7qQnfipglXk2PcDeCKto_XoKpvUTXvqAim9d8K8rDd8abwWrcCJf7kKr_jG0JnhZdTnmdvXUX1HN3q7ZT-ZEM993zjoLP/s1600/g8017.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="694" data-original-width="537" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_yeDy9VPfrmKUrRBH_HrXE8Ei7-SFudIKeAFv5FpBb10We5y7qQnfipglXk2PcDeCKto_XoKpvUTXvqAim9d8K8rDd8abwWrcCJf7kKr_jG0JnhZdTnmdvXUX1HN3q7ZT-ZEM993zjoLP/s1600/g8017.png" /></a></div><hr /><h3>Latihan</h3><a href="http://www.spoj.com/problems/MKTHNUM/">SPOJ MKTHNUM</a><br />
Petunjuk: soal ini bukan soal range tree "straight-forward". Namun Anda hanya butuh sebuah observasi untuk dapat mengubah soal ini menjadi "straight-forward". Sorot teks di bawah ini untuk <i>spoiler</i>:<br />
Spoiler 1: <span style="color: #353535;">lakukan binary search the answer. Coba tebak jawabannya, lalu cek berapa banyak elemen yang berada di dalam indeks yang diberikan, dan lebih kecil dari angka yang Anda tebak.</span><br />
Spoiler 2: <span style="color: #353535;">jika elemen ke-i bernilai vi, maka elemen tersebut dapat direpresentasikan sebagai titik di koordinat (i, vi).</span><br />
<a href="https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3767"><br />
UVa 12345 - Dynamic len(set(a[L:R]))</a><br />
Spoiler 1: <span style="color: #353535;">jika elemen ke-i dan ke-j memiliki nilai yang sama, dan tidak ada elemen di antaranya yang bernilai sama juga, maka tambahkan titik bernilai (i ,j) ke dalam range tree. </span><br />
Spoiler 2: <span style="color: #353535;">Banyaknya elemen unik pada suatu rentang a..b adalah seluruh elemen pada rentang tersebut, dikurangi titik-titik pada daerah (a,?)..(?,b).</span><br />
<br />
<hr /><h3>Penutup</h3>Pembahasan tentang range tree ini masih di bagian permukaannya. Kita belum memasuki bagian membuat segment tree di dalam range tree. <a href="http://kupaskode.blogspot.com/2018/06/struktur-data-range-tree-rmq.html">Tulisan yang akan datang</a> akan membahas tentang hal tersebut. Semoga bermanfaat!<br />
<br />
</div>William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0tag:blogger.com,1999:blog-2894426456296176040.post-56186652038474493222017-08-16T03:40:00.000+07:002017-08-18T03:55:43.264+07:00Pelatih Basket Gendut<div dir="ltr" style="text-align: left;" trbidi="on">
(sesekali menulis yang tidak berkaitan dengan pemrograman)<br />
<br />
Jika Anda pernah menonton film Slam Dunk, maka Anda mengenal Pak Anzai.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKRexmgwcfKjS6yUaaKV51vq6GrG2Rea38PDp1c-M-YVqDcjlzKcLsij1xoCVO407hMbHem2KJ2LIhJRnR3iBH1MMDwoiEngx35h4tk2WSi985cT-WpLyWtdukUYbQIyqEvVxdpYxtxLQ-/s1600/anzai.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="314" data-original-width="221" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKRexmgwcfKjS6yUaaKV51vq6GrG2Rea38PDp1c-M-YVqDcjlzKcLsij1xoCVO407hMbHem2KJ2LIhJRnR3iBH1MMDwoiEngx35h4tk2WSi985cT-WpLyWtdukUYbQIyqEvVxdpYxtxLQ-/s200/anzai.jpg" width="140" /></a></div>
<div style="text-align: center;">
Pak Anzai dari cerita Slam Dunk <br />
(dari <a href="http://annelialady.blogspot.com/">http://annelialady.blogspot.com</a>)</div>
<br />
Pak Anzai, adalah pelatih bola basket pada film tersebut. Dulunya adalah seorang pemain, lalu sekarang sudah tua, gendut, dan pensiun. Saya mulai merasa diri saya seperti itu.<br />
<br />
Sekarang saya sudah pensiun untuk competitive programming. Meskipun demikian, saya akan tetap menuliskan pengetahuan yang saya miliki, baik pada blog ini atau media lainnya.</div>
William Gozalihttp://www.blogger.com/profile/14799309612446697627noreply@blogger.com0