Kamis, 30 April 2020

Pembahasan Semifinal JOINTS PCS 2019

Soalnya dapat Anda cek di https://tlx.toki.id/problems/joints-2019-pcs-semifinal.
Seperti biasa, coba kerjakan sendiri dulu sebelum membaca tulisan ini.


Coin Exchange

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.

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.

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.


Hitung Kata

Ini soal DP yang cukup sederhana. Idenya seperti longest common subsequence (LCS), tetapi kali ini seluruh karakter di string kedua wajib dicocokkan. Apabila berhasil dicocokkan, tambahkan kemungkinan jawaban dengan 1.

Misalkan kedua string memiliki indeks yang dimulai dari 1. Seperti pada LCS, kita proses karakter demi karakter dari belakang.
Jika \(f(i, j)\) menyatakan banyaknya subsequence dari \(S[1..i]\) yang sama dengan \(P[1..j]\), maka pilihannya adalah:
  • Buang \(S[j]\), sehingga banyaknya cara pencocokan menjadi \(f(i-1, j)\).
  • 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]\).
Base case dicapai apabila:
  • \(j\) bernilai 0, yang artinya pencocokan berhasil.
  • \(i\) bernilai 0, tetapi \(j\) bukan 0, yang artinya pencocokan tidak berhasil.
Secara matematis, bisa dituliskan:
\[ f(i,j) = \left\{\begin{array}{ll}
1 & ,j = 0\\
0 & ,i=0 \land j>0\\
f(i-1,j) + f(i-1,j-1) & ,S[i]=P[j]\\
f(i-1,j) & ,S[i] \ne P[j]\\
\end{array}\right. \]
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.

Karena malas menggunakan big integer, saya coba submit saja. Ternyata accepted :))
Kompleksitas solusi ini \(O(|S||P|)\).


Kerja Pak Blangkon

Observasi yang penting adalah setiap pekerjaan dapat diselesaikan dalam 1 hari, sehingga pekerjaan yang lebih menguntungkan pasti dipilih terlebih dahulu.

Senin, 27 April 2020

Pembahasan Penyisihan JOINTS PCS 2019

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.

Soalnya dapat Anda temui di web TLX: https://tlx.toki.id/problems/joints-2019-pcs-penyisihan

Sangat dianjurkan bagi Anda untuk mencobanya terlebih dahulu. Kalau tersendat baru liat pembahasan di sini :-)

Kode solusi saya cantumkan di akhir tulisan.


Dolan 2019

Deskripsi soal inilah yang membuat saya tertarik mengerjakan seluruh set soal.

Cukup proses setiap barisnya satu per satu sambil mencatat kategori jawaban terbaik sejauh ini. Supaya mudah, kodekan kategori jawaban sebagai berikut:
0. BALIK AJA
1. TERPAKSA
2. SENANG

Awalnya inisialisasi kategori jawaban dengan 0 (untuk "BALIK AJA"). Untuk setiap baris, lakukan iterasi dari kiri ke kanan sambil mencatat:
  1. Karakter terakhir sebelum 'O'. Simpan karakter ini di variabel "prev".
  2. Banyaknya karakter 'O' konsekutif sampai sejauh ini. Anggap banyaknya ini disimpan di variabel "count".
Setiap kali ditemukan karakter yang bukan 'O', simpan karakter itu di variabel "next" dan periksa apakah:
  1. count ≥ N+2. Kalau iya, berarti geng CJ pasti bisa "SENANG".
  2. 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.
  3. count = N dan nilai dari prev dan next sama-sama 'L'. Kalau iya berarti "SENANG", dan kalau tidak berarti kategori 1 menjadi kandidat jawaban.
Jangan lupa melakukan pemeriksaan kalau sudah mencapai ujung kanan kolom dan karakternya masih juga 'O'. Asumsikan saja next bernilai 'L'.

Terakhir, tinggal cetak kategori jawaban terbaik. Kompleksitas solusinya \(O(RC)\).


KPK

Solusi brutal jelas akan mendapat TLE, berhubung \(3^N\) dengan \(N=10^6\) terlalu besar.

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:
\[x = k 3^p\]
Mengingat masa SD, KPK dari \(k 3^p\) dan \(3^N\) adalah \(k 3^N\).
Bila \(x\) dimulai dari 1 sampai dengan \(3^N\), maka nilai yang ingin dicari adalah:
\[ \begin{array}{c} k_1 3^N + k_2 3^N + k_3 3^N + ... + k_{3^N} 3^N
\\ = 3^N (k_1 + k_2 + k_3 + ... + k_{3^N} ) \end{array} \]