05. Perulangan
Komputer unggul pada pekerjaan yang diulang ribuan kali tanpa bosan. Perulangan adalah alat untuk menyuruhnya melakukan itu — dan hampir semua soal OSN membutuhkannya.
for
Bentuk for dipakai ketika banyaknya pengulangan sudah
diketahui:
for (int i = 1; i <= 5; i++) {
cout << i << " ";
}
cout << "\n";1 2 3 4 5
Tiga bagian di dalam kurung, dipisahkan titik koma:
| Bagian | Kapan dijalankan | Contoh |
|---|---|---|
| awal | sekali, sebelum perulangan | int i = 1 |
| syarat lanjut | sebelum setiap putaran | i <= 5 |
| perubahan | sesudah setiap putaran | i++ |
Urutan pengerjaannya bukan dari kiri ke kanan begitu saja, melainkan seperti ini: awal dijalankan sekali, lalu syarat diperiksa, lalu isi dijalankan, lalu perubahan, lalu kembali memeriksa syarat, dan seterusnya. Telusuri contoh di atas:
| Pemeriksaan ke- | i saat diperiksa |
i <= 5? |
dicetak | sesudah i++ |
|---|---|---|---|---|
| 1 | 1 | ya | 1 |
2 |
| 2 | 2 | ya | 2 |
3 |
| 3 | 3 | ya | 3 |
4 |
| 4 | 4 | ya | 4 |
5 |
| 5 | 5 | ya | 5 |
6 |
| 6 | 6 | tidak | — | perulangan berhenti |
Perhatikan dua hal yang sering mengejutkan pemula: syarat diperiksa
sebelum isi dijalankan (jadi isi bisa tidak pernah
dijalankan sama sekali), dan sesudah perulangan selesai nilai
i sudah melewati batasnya.
Variabel i yang dibuat di dalam
for (int i = ...) hanya hidup selama perulangan itu.
Sesudahnya ia tidak ada lagi, dan memakainya menghasilkan
'i' was not declared in this scope.
Variasi yang sering dipakai:
for (int i = 0; i < n; i++) // 0 sampai n-1, paling umum untuk array
for (int i = n; i >= 1; i--) // mundur
for (int i = 0; i < n; i += 2) // melompat duaBanyaknya putaran bisa dihitung tanpa menjalankan programnya, dan ini yang perlu dibiasakan sejak sekarang karena Bab 10 memakainya terus-menerus:
| Bentuk | Banyak putaran |
|---|---|
for (int i = 0; i < n; i++) |
n |
for (int i = 1; i <= n; i++) |
n |
for (int i = 0; i <= n; i++) |
n + 1 |
for (int i = 2; i <= n; i++) |
n - 1 |
for (int i = 0; i < n; i += 2) |
sekitar n / 2 |
Baris ketiga adalah kesalahan satu langkah
(off-by-one) yang paling sering: mengganti <
menjadi <= menambah satu putaran, dan pada array putaran
tambahan itu menyentuh kotak yang tidak ada.
Di lomba, for (int i = 0; i < n; i++) jauh lebih
sering dipakai daripada i = 1; i <= n. Alasannya: indeks
array di C++ dimulai dari 0. Biasakan bentuk itu supaya tidak sering
meleset satu langkah.
while
while dipakai ketika banyaknya pengulangan
tidak diketahui sejak awal, hanya syarat berhentinya
yang diketahui:
int n = 1234;
int banyak_digit = 0;
while (n > 0) {
n /= 10;
banyak_digit++;
}
cout << banyak_digit << "\n"; // 4Kalau syaratnya tidak pernah menjadi salah, program berputar
selamanya dan di penilai daring hasilnya Time Limit Exceeded.
Pastikan ada baris di dalam perulangan yang mengubah nilai yang
diperiksa — pada contoh di atas, n /= 10.
Menelusuri contoh di atas untuk n = 1234:
| Pemeriksaan | n |
n > 0? |
n /= 10 |
banyak_digit |
|---|---|---|---|---|
| 1 | 1234 | ya | 123 | 1 |
| 2 | 123 | ya | 12 | 2 |
| 3 | 12 | ya | 1 | 3 |
| 4 | 1 | ya | 0 | 4 |
| 5 | 0 | tidak | — | 4 |
Memilih for atau
while
| Kalau di soal tertulis ... | Pakai |
|---|---|
"untuk setiap dari n data" |
for |
"ulangi k kali" |
for |
| "selama masih ada sisa", "sampai bernilai nol" | while |
| "sampai masukan habis" | while |
| "sampai ditemukan" | while atau for + break |
Keduanya sama kuat — apa pun yang bisa ditulis dengan
for bisa ditulis dengan while, dan sebaliknya.
Yang dipilih adalah yang membuat maksudnya paling jelas terbaca.
do ... while
Sama seperti while, tetapi isinya dijalankan minimal
sekali karena syaratnya diperiksa di akhir:
int pilihan;
do {
cin >> pilihan;
} while (pilihan < 1 || pilihan > 3);Bentuk ini jarang dipakai untuk soal penilai daring, tetapi berguna untuk program interaktif.
break dan
continue
breakkeluar dari perulangan seketika.continuemelompat ke putaran berikutnya.
for (int i = 1; i <= 100; i++) {
if (i % 3 != 0) continue; // lewati yang bukan kelipatan 3
if (i > 20) break; // berhenti begitu melewati 20
cout << i << " ";
}3 6 9 12 15 18
Buka
exercises/05/02-while.cpp.
Ubah angka awalnya menjadi 0, compile ulang, dan perhatikan
bahwa hasil hitungan digitnya 0 — bukan 1. Ini
kasus tepi yang sering terlupakan di soal.
Pola akumulasi
Pola paling sering di seluruh buku ini: siapkan sebuah penampung sebelum perulangan, ubah isinya di setiap putaran, pakai hasilnya sesudah perulangan.
long long total = 0; // penampung
for (int i = 1; i <= n; i++) {
total += i; // diubah tiap putaran
}
cout << total << "\n"; // dipakai sesudahnyaTiga bagian itu — siapkan, ubah, pakai — selalu ada. Yang paling sering salah adalah bagian "siapkan", karena nilai awalnya berbeda untuk setiap jenis akumulasi:
| Yang dihitung | Nilai awal penampung | Operasi di dalam perulangan |
|---|---|---|
| jumlah | 0 |
total += x |
| hasil kali | 1 |
hasil *= x |
| banyaknya yang cocok | 0 |
banyak++ |
| nilai terbesar | elemen pertama, atau bilangan sangat kecil | if (x > besar) besar = x |
| nilai terkecil | elemen pertama, atau bilangan sangat besar | if (x < kecil) kecil = x |
Nilai awal 0 untuk hasil kali membuat jawabannya selalu
nol; nilai awal 0 untuk "terbesar" membuat jawabannya salah
bila semua data negatif. Keduanya kesalahan klasik yang lolos dari
contoh kasus di soal dan baru ketahuan saat dinilai.
Buka
exercises/05/03-akumulasi.cpp.
Ubah long long faktorial = 1; menjadi = 0,
compile ulang, dan jalankan untuk n = 10. Baris jumlah dan
cacahnya tetap benar, tetapi faktorialnya menjadi 0 — satu
nilai awal yang salah cukup untuk merusak satu jawaban.
Variasi yang perlu kamu hafal bentuknya:
// menghitung berapa banyak yang memenuhi syarat
int banyak = 0;
for (int i = 1; i <= n; i++) if (i % 7 == 0) banyak++;
// mencari yang terbesar
int besar = -1000000000;
for (int i = 0; i < n; i++) { cin >> x; if (x > besar) besar = x; }
// perkalian beruntun (faktorial)
long long hasil = 1;
for (int i = 2; i <= n; i++) hasil *= i;Penampung untuk penjumlahan hampir selalu long long.
Menjumlahkan 100.000 bilangan yang masing-masing sampai 10⁹ menghasilkan
10¹⁴ — int meluap jauh sebelum itu.
Memproses digit
Gabungan % 10 dan / 10 dari Bab 03 di dalam
while memberi cara membaca sebuah bilangan digit demi
digit, dari belakang:
long long n = 4071;
int jumlah = 0;
while (n > 0) {
int digit = n % 10; // ambil digit terakhir
jumlah += digit;
n /= 10; // buang digit terakhir
}
cout << jumlah << "\n"; // 12Perulangan bersarang
Perulangan boleh berada di dalam perulangan. Yang dalam berputar penuh untuk setiap putaran yang luar.
for (int baris = 1; baris <= 4; baris++) {
for (int kolom = 1; kolom <= baris; kolom++) {
cout << "*";
}
cout << "\n";
}*
**
***
****
Cara membacanya: perulangan luar menentukan berapa baris, perulangan dalam menentukan isi satu baris. Untuk contoh di atas:
baris |
kolom berjalan dari–sampai |
bintang tercetak |
|---|---|---|
| 1 | 1 … 1 | 1 |
| 2 | 1 … 2 | 2 |
| 3 | 1 … 3 | 3 |
| 4 | 1 … 4 | 4 |
Seluruhnya 1 + 2 + 3 + 4 = 10 bintang. Perhatikan juga letak
cout << "\n": ia berada di dalam perulangan luar
tetapi di luar perulangan dalam. Memindahkannya satu
tingkat ke dalam membuat setiap bintang berpindah baris; memindahkannya
ke luar membuat semuanya menjadi satu baris panjang.
Jumlah pekerjaan ikut berlipat: dua perulangan bersarang yang
masing-masing sampai n mengerjakan sekitar n² langkah.
Untuk n = 1000 itu sejuta langkah (cepat), untuk
n = 100000 itu sepuluh miliar (jauh terlalu lambat). Bab 10
membahas cara memperkirakannya sebelum menulis kode.
Buka
exercises/05/04-bersarang.cpp
dan ubah agar segitiganya terbalik (baris pertama empat bintang, baris
terakhir satu).
Pola banyak kasus uji
Banyak soal lomba memberi jumlah kasus uji di baris pertama. Bentuk bakunya:
int t;
cin >> t;
while (t--) {
int a, b;
cin >> a >> b;
cout << a + b << "\n";
}t-- memberikan nilai t sekarang kepada
while, lalu menguranginya satu. Saat t
mencapai 0, perulangan berhenti.
Pada soal banyak kasus uji, semua variabel penampung harus disiapkan
ulang di dalam perulangan while (t--),
bukan di luarnya. Penampung yang disiapkan sekali di luar akan membawa
sisa hitungan kasus sebelumnya ke kasus berikutnya — jawaban kasus
pertama benar, sisanya salah. Ini penyebab nilai sebagian yang paling
sering pada soal berkasus uji banyak.
Buka
exercises/05/06-banyak-kasus.cpp.
Tambahkan int total = 0; sebagai baris pertama di
dalam while (t--) dan ubah baris cetaknya menjadi
total += a + b; cout << total << "\n";.
Jalankan dengan contoh masukan di bagian atas file; keluarannya
3, 30, 14. Lalu pindahkan
int total = 0; ke atas while, jalankan lagi:
keluarannya berubah menjadi 3, 33,
47 karena hitungan kasus sebelumnya ikut terbawa.
Kalau perulangan tidak mau berhenti
Program yang menggantung tanpa mencetak apa-apa hampir selalu berarti satu dari tiga hal:
- Nilai yang diperiksa tidak pernah berubah. Periksa apakah ada baris di dalam perulangan yang benar-benar mengubahnya.
- Perubahannya menjauhi syarat berhenti. Misalnya
i--pada perulangan yang menungguimencapain. - Programnya menunggu masukan. Bukan perulangan tak
berujung, melainkan
cin >>yang menanti angka yang tidak pernah kamu ketik.
Hentikan program yang menggantung dengan Ctrl + C.
Untuk melihat apa yang sebenarnya terjadi, sisipkan sementara
cerr << i << " " << n << "\n"; di
dalam perulangan — cerr dipakai, bukan cout,
supaya tidak mengotori keluaran yang dinilai (Bab 10).
Pekerjaan rumah
Soal 1 — Jumlah 1 sampai N
Buka
exercises/05/homework/01-jumlah-n.cpp.
Baca n (sampai 1.000.000), cetak
1 + 2 + ... + n. Perhatikan tipe penampungnya.
Soal 2 — Jumlah digit
Buka
exercises/05/homework/02-jumlah-digit.cpp.
Baca satu bilangan bulat positif (bisa sampai 10¹⁸), cetak jumlah semua
digitnya. Contoh: 4071 menghasilkan 12.
Soal 3 — Tabel perkalian
Buka
exercises/05/homework/03-tabel-perkalian.cpp.
Baca n, cetak tabel perkalian n × n. Setiap
baris berisi n bilangan dipisahkan satu spasi, tanpa spasi
tambahan di akhir baris.
Soal 4 — Segitiga angka
Buka
exercises/05/homework/04-segitiga-angka.cpp.
Baca n, cetak pola berikut untuk n = 4:
1
12
123
1234
Tantangan — Uji bilangan prima
Buka
exercises/05/homework/05-prima.cpp.
Baca satu bilangan bulat n (sampai 10¹²), cetak
PRIMA atau BUKAN. Memeriksa semua bilangan
dari 2 sampai n-1 terlalu lambat; berhenti di akar
n, karena kalau n = a × b maka salah satu
faktornya pasti tidak lebih besar dari akar n. Pakai
for (long long i = 2; i * i <= n; i++). Jangan lupa
n = 1 bukan prima.
Kesalahan yang paling sering
| Yang ditulis | Akibatnya |
|---|---|
for (int i = 0; i <= n; i++) pada array |
menyentuh a[n] yang tidak ada |
for (int i = 0; i < n; i++); |
titik koma menutup perulangan; isinya jalan sekali |
penampung int untuk jumlah besar |
meluap menjadi negatif |
penampung disiapkan di luar while (t--) |
kasus uji kedua dan seterusnya salah |
| tidak ada baris yang mengubah syarat | perulangan tak berujung, Time Limit Exceeded |
besar = 0 padahal data bisa negatif |
jawaban selalu 0 |
cout << "\n" salah tingkat |
susunan baris keluaran berantakan |
Tips OSN
Pola banyak kasus uji harus otomatis. Sebagian soal
OSN membungkus satu soal kecil ke dalam T kasus uji.
Bentuknya selalu sama, dan sebaiknya sudah hafal:
int t;
cin >> t;
while (t--) {
// baca satu kasus, cetak satu jawaban
}Dua kesalahan yang sering muncul di pola ini: variabel penampung yang lupa dikembalikan ke nol di awal setiap kasus, dan jawaban yang dikumpulkan dulu lalu dicetak di akhir padahal soal meminta satu baris per kasus.
Perkirakan banyaknya langkah sebelum menulis kode. Aturan kasar yang dipakai sepanjang buku ini: mesin penilai menyelesaikan sekitar 10⁸ operasi sederhana per detik. Dari situ, batasan pada soal langsung memberi tahu bentuk penyelesaian yang muat:
Batasan N |
Perulangan yang masih muat dalam 1 detik |
|---|---|
| ≤ 1.000 | dua tingkat bersarang (N² = 10⁶) |
| ≤ 100.000 | satu tingkat, atau N log N |
| ≤ 1.000.000 | satu tingkat saja |
| ≤ 10¹⁸ | tidak boleh ada perulangan atas N |
Batasan 10¹⁸ adalah isyarat yang jelas: yang diminta rumus atau pengulangan atas digit-nya, bukan atas nilainya.
Nilai antara bisa jauh lebih besar daripada masukan.
Soal yang menyatakan N ≤ 10⁶ tidak menjamin semua angka di
dalam programmu tetap kecil. Deret Collatz di bawah adalah contohnya:
mulai dari 704.511, deretnya sempat mencapai 56.991.483.520 — melewati
int lebih dari dua puluh kali lipat.
Perulangan yang tidak berhenti dinilai Time Limit
Exceeded, bukan error. Kalau jawabanmu benar di komputer
sendiri tetapi kena TLE di semua berkas uji, curigai syarat berhenti
yang tidak pernah tercapai — misalnya while (n != 1) untuk
n yang bisa bernilai 0.
Keluaran banyak baris lebih baik dikumpulkan. Untuk
soal yang mencetak ratusan ribu baris, mencetak dengan endl
di setiap perulangan membuat program berkali-kali lipat lebih lambat.
Pakai "\n".
for (int i = 0; i < n; i++) dengan n
bertipe long long bernilai 3 × 10⁹ tidak akan pernah
berhenti: i bertipe int meluap menjadi negatif
sebelum mencapai n. Samakan tipe pencacah dengan tipe
batasnya.
Ringkasan
fordipakai saat banyaknya putaran diketahui,whilesaat hanya syarat berhentinya yang diketahui.- Tiga bagian
for: awal (sekali), syarat (sebelum tiap putaran), perubahan (sesudah tiap putaran). for (int i = 0; i < n; i++)berputarnkali dan cocok dengan indeks array; mengganti<menjadi<=menambah satu putaran.- Pola akumulasi: siapkan penampung, ubah tiap putaran, pakai sesudahnya. Nilai awalnya berbeda untuk jumlah, hasil kali, dan nilai terbesar.
while (n > 0) { n % 10; n /= 10; }membaca sebuah bilangan digit demi digit dari belakang.- Perulangan bersarang mengerjakan sekitar n² langkah — cukup untuk
nribuan, tidak untuknratusan ribu. - Penampung pada soal banyak kasus uji harus disiapkan ulang di setiap kasus.
Latihan bergaya OSN
Ketiga soal berikut dikerjakan dengan perulangan saja — belum perlu array. Soal ketiga memakai pola banyak kasus uji yang akan sering kamu temui di OSN.
Kerangka ketiganya ada di
exercises/05/osn/.
Soal 1 — Deret Collatz
Ambil sebuah bilangan bulat positif N. Selama angkanya
belum sama dengan 1, terapkan aturan berikut berulang-ulang:
- kalau angkanya genap, bagi dua;
- kalau angkanya ganjil, kalikan tiga lalu tambah satu.
Satu kali penerapan aturan dihitung sebagai satu langkah. Hitung berapa langkah yang diperlukan sampai angkanya menjadi 1.
Format masukan. Satu baris berisi satu bilangan
bulat N.
Format keluaran. Satu baris berisi banyaknya langkah.
Batasan. 1 ≤ N ≤ 10⁶. Untuk semua
N pada batasan ini, deretnya dijamin mencapai 1.
Contoh masukan
6
Contoh keluaran
8
Penjelasan. Deretnya 6 → 3 → 10 → 5 → 16 → 8 → 4 → 2
→ 1, yaitu delapan langkah. Untuk N = 1, jawabannya 0
karena angkanya sudah 1 sejak awal.
Kerangkanya:
exercises/05/osn/01-collatz.cpp
Soal 2 — Palindrom Angka
Sebuah bilangan disebut palindrom kalau urutan digitnya dibaca dari depan sama dengan dibaca dari belakang. Misalnya 12321 dan 7 adalah palindrom, sedangkan 12345 dan 10 bukan.
Format masukan. Satu baris berisi satu bilangan
bulat N.
Format keluaran. Satu baris berisi YA
kalau N palindrom, atau TIDAK kalau bukan.
Batasan. 1 ≤ N ≤ 10¹⁸. Bilangan tidak
boleh dibaca sebagai teks.
Contoh masukan
12321
Contoh keluaran
YA
Penjelasan. Digit 12321 dibaca dari belakang tetap
12321. Perhatikan batasan 10¹⁸: bilangan sebesar itu tidak muat di
int, dan perulangan sebanyak N kali jelas
tidak mungkin — yang diulang adalah digitnya, dan digitnya paling banyak
19.
Kerangkanya:
exercises/05/osn/02-palindrom-angka.cpp
Soal 3 — Banyak Faktor
Untuk setiap bilangan yang diberikan, hitung ada berapa bilangan bulat positif yang membaginya habis.
Format masukan. Baris pertama berisi satu bilangan
bulat T, banyaknya kasus uji. T baris
berikutnya masing-masing berisi satu bilangan bulat N.
Format keluaran. T baris. Baris
ke-i berisi banyaknya faktor positif dari N
pada kasus uji ke-i.
Batasan.
- 1 ≤
T≤ 50 - 1 ≤
N≤ 100.000
Contoh masukan
3
12
1
97
Contoh keluaran
6
1
2
Penjelasan. Faktor 12 adalah 1, 2, 3, 4, 6, dan 12 — ada enam. Bilangan 1 hanya punya satu faktor, yaitu dirinya sendiri. Bilangan 97 adalah bilangan prima, jadi faktornya hanya 1 dan 97.
Kerangkanya:
exercises/05/osn/03-banyak-faktor.cpp
Bingung atau sudah selesai? Buka halaman pembahasan PR.