Pemrograman Kompetitif Lanjutan 05 Dynamic Programming Lanjutan
Pemrograman Kompetitif Lanjutan 05 Dynamic Programming Lanjutan
1/25
Pendahuluan
2/25
Motivasi
3/25
Mengenal Bitmask
4/25
Operasi Bitmask
• Terdapat beberapa operasi dua bitmask. Jika x dan y adalah
dua bitmask.
• x or y akan mengembalikan bitmask yang merupakan bitmask
yang merepresentasikan gabungan dari himpunan yang
direpresentasikan oleh x dan y .
• Sebagai contoh, 25 or 3 = 27 karena gabungan dari {0, 3, 4}
dan {0, 1} adalah {0, 1, 3, 4}.
• Pada C++, operasi ini dihitung dengan x | y.
• x and y akan mengembalikan bitmask yang merupakan
bitmask yang merepresentasikan irisan dari himpunan yang
direpresentasikan oleh x dan y .
• Sebagai contoh, 25 and 3 = 1 karena irisan dari {0, 3, 4} dan
{0, 1} adalah {0}.
• Pada C++, operasi ini dihitung dengan x & y.
5/25
Operasi Bitmask (lanj.)
6/25
DP bitmask: Travelling Salesman Problem
7/25
Solusi TSP menggunakan DP Top-Down
int solve(int mask, int u) {
if (mask == (1 << N) - 1) {
return 0; // seluruh kota sudah dikunjungi
}
if (computed[mask][u]) {
return memo[mask][u];
}
computed[mask][u] = true;
int res = INT_MAX;
for (int v : adj[u]) {
if ((mask & (1 << v)) == 0) { // kota v belum
dikunjungi
res = min(res, solve(mask | (1 << v), mask) +
length[u][v]);
}
}
return memo[mask][u] = res;
}
8/25
Kompleksitas Solusi TSP
9/25
DP Broken Profile: Contoh Soal
Diberikan grid yang berisi R × C sel. Setiap sel berisi sebuah
bilangan bulat. Anda ingin mengambil beberapa sel sedemikian
sehingga total bilangan bulat pada sel yang Anda ambil
semaksimum mungkin dan tidak terdapat tiga sel yang diambil
bersebelahan dengan bentuk
10/25
DP Broken Profile
• Kita dapat mencoba apakah setiap sel akan diambil atau tidak
dengan urutan sel
(1, 1), (1, 2), . . . , (1, C ),
(2, 1), (2, 2), . . . , (2, C ),
...
(R, 1), (R, 2), . . . , (R, C ).
• Dengan menyimpan apakah C sel terakhir diambil atau tidak
dan lokasi sel sekarang, kita dapat mengetahui apakah sel
sekarang dapat diambil atau tidak.
• Selain sel pada kolom pertama, jika satu sel sebelumnya (sel di
kirinya) dan C sel sebelumnya (sel di atasnya) sudah diambil,
maka sel sekarang tidak dapat diambil.
• State C sel terakhir dapat direpresentasikan menggunakan
bitmask. Bit ke-i pada bitmask merupakan bit 1 jika dan
hanya jika i sel sebelumnya diambil.
11/25
Solusi menggunakan DP Top-Down
int dp(int mask, int now) {
if (now.r == R + 1) {
return 0; // seluruh sel telah diiterasi.
}
if (computed[mask][now]) {
return memo[mask][now];
}
computed[mask][now] = true;
int next_mask = (mask << 1) % (1 << C);
int res = dp(next(now), next_mask); // sel sekarang
tidak diambil.
if (now.c == 1 || !(mask & 1) || !(mask & (1 << (C -
1))) {
// antara sel di kirinya atau sel di atasnya tidak
diambil.
res = max(res, dp(next(now), next_mask + 1) +
value[now]);
}
return memo[mask][now] = res;
}
12/25
Kompleksitas Solusi
13/25
DP Sum over Subset
14/25
DP Sum over Subset (lanj.)
15/25
DP Sum over Subset (lanj.)
• Perhatikan bahwa S(M, i) dapat dihitung secara rekursif
sebagai berikut:
• S(M, 0) = {M}
• S(M, i) = S(M, i − 1) jika bit ke-(i − 1) pada M adalah 0.
• S(M, i) = S(M, i − 1) ∪ S(M − 2i − 1, i − 1) jika bit ke-(i − 1)
pada M adalah 1. Perhatikan bahwa
S(M, i − 1) ∩ S(M − 2i − 1, i − 1) = ∅
• Kita dapat definisikan DP(M, i) sebagai jumlah F [x] untuk
setiap x yang merupakan anggota dari S(M, i). Sehingga,
DP(M, i) dapat dihitung sebagai beriukt:
• DP(M, 0) = F [M]
• DP(M, i) = DP(M, i − 1) jika bit ke-(i − 1) pada M adalah 0.
• DP(M, i) = DP(M, i − 1) + DP(M − 2i − 1, i − 1) jika bit
ke-(i − 1) pada M adalah 1.
16/25
Kompleksitas Solusi
17/25
DP pada complete binary tree: Contoh Soal
18/25
DP pada complete binary tree
• Persoalan ini dapat diselesaikan dengan mendefinisikan
f (u, root, take), dengan u adalah node pada tree, root adalah
sebuah boolean, dan take adalah sebuah bilangan bulat,
sebagai berikut:
• Kita ingin mengambil take node yang merupakan subtree dari
u.
• Node u dapat diambil jika dan hanya jika root = true.
• Fungsi ini mengembalikan maksimum total bobot node yang
dapat diambil.
• Fungsi ini dapat dihitung dengan mencoba:
• apakah node u akan diambil,
• ada berapa node yang ingin diambil yang merupakan subtree
dari anak u pertama, dan
• ada berapa node yang ingin diambil yang merupakan subtree
dari anak u kedua.
19/25
DP pada complete binary tree (lanj.)
int f(int u, bool root, int take) {
if (take == 0 || u == NULL) {
return 0;
}
if (computed[u][root][take]) {
return memo[u][root][take];
}
memo[u][root][take] = true;
res = INT_MIN;
for (int i = 0; i <= take; ++i) {
res = max(res, f(child_l(u), true, i) + f(child_r(u),
true, take - i));
if (i < take) {
// Mengambil node u, sehingga node child_l(u) dan
node child_r(u) tidak dapat diambil.
res = max(res, w[u]
+ f(child_l(u), false, i)
+ f(child_r(u), false, take - i - 1));
}
}
return memo[u][root][take] = res;
}
20/25
Kompleksitas Solusi
• Jawaban yang diinginkan adalah f (R, false, K ) dengan R
adalah root tree.
• Banyaknya kemungkinan parameter pada fungsi f di slide
sebelumnya adalah O(N × K ) dengan N adalah banyaknya
node.
• Setiap fungsi f mencoba seluruh pembagian take ke dua
subtree, sehingga membutuhkan waktu O(K ).
• Sehingga, total kompleksitas dari solusi dynamic programming
ini adalah O(N × K 2 ).
• Bagaimana jika tree yang diberikan bukan merupakan
complete binary tree?
• Pembagian take tidak dapat dibagikan hanya ke subtree kiri
dan subtree kanan.
21/25
DP pada Tree: Left-Child Right-Sibling
• Ubah tree agar setiap node hanya memiliki satu anak. Sisa
anak-anak lainnya akan menjadi saudara (sibling ) dari anak
tersebut.
• Setiap node hanya akan memiliki paling banyak dua node
lainnya yang terhubung (selain parent).
• Untuk transisi ke sibling , state root tidak berubah karena
sibling dari node u sebenarnya memiliki parent yang sama
dengan node.
22/25
DP pada Tree: Left-Child Right-Sibling (lanj.)
int g(int u, bool root, int take) {
if (take == 0 || u == NULL) {
return 0;
}
if (computed[u][root][take]) {
return memo[u][root][take];
}
memo[u][root][take] = true;
res = INT_MIN;
for (int i = 0; i <= take; ++i) {
res = max(res, g(child(u), true, i) + g(sibling(u),
root, take - i));
if (i < take) {
// Mengambil node u, sehingga node child(u) tidak
dapat diambil.
res = max(res, w[u]
+ g(child(u), false, i)
+ g(sibling(u), root, take - i - 1));
}
}
return memo[u][root][take] = res;
}
23/25
DP pada Tree: Mengubah menjadi Left-Child
Right-Sibling
void dfs(int u, int parent) {
child[u] = NULL;
sibling[u] = NULL;
int last_child = NULL;
for (int x : adj[u]) {
if (x == parent) {
continue;
}
if (child[u] == NULL) {
child[u] = x;
}
if (last_child != NULL) {
sibling[last_child] = x;
}
dfs(x, u);
last_child = x;
}
}
24/25
Kompleksitas Solusi
25/25