03. Variabel, tipe data, dan masukan — Pembahasan PR

Berkas .cpp jawabannya ada di exercises/03/homework/solutions/.

Halaman ini dibuka sesudah mencoba sendiri. Bandingkan jalan pikiranmu dengan bagian Cara berpikir, bukan kodenya baris per baris: dua kode yang berbeda bentuk bisa sama-sama benar, dan yang perlu ditiru adalah cara sampai ke sana. Bagian Uji di tiap soal berisi masukan dan keluaran yang sudah diperiksa — jalankan jawabanmu sendiri dengan masukan itu sebelum merasa selesai.

Menjalankan salah satu jawabannya:

g++ -std=c++17 -O2 -o jawab exercises/03/homework/solutions/01-jumlah-dua.cpp
./jawab

Soal 1 — Jumlah dua bilangan

Soal. Baca dua bilangan bulat, cetak jumlahnya, tanpa teks tambahan.

Cara berpikir. Bentuk baku semua soal penilai daring: deklarasikan variabel, baca dengan cin, cetak hasil dengan cout. Yang membedakan jawaban benar dan salah di sini bukan logikanya, melainkan disiplin format — satu kalimat pengantar saja sudah membuat nilainya nol.

Pembahasan.

int a, b;
cin >> a >> b;
cout << a + b << "\n";

Uji.

Masukan Keluaran
3 4 7
-5 5 0
1000000000 1000000000 2000000000

Baris ketiga adalah kasus tepi yang layak diperhatikan: 2 × 10⁹ masih muat di int, tetapi hanya nyaris (batasnya 2.147.483.647). Kalau soalnya menyebut batas lebih tinggi sedikit saja, jawabannya harus long long.

Kesalahan umum.

  • Menambahkan cout << "Masukkan dua bilangan: ";.
  • Mencetak "Jumlahnya adalah " sebelum angkanya.
  • Memakai int padahal soal aslinya bisa saja menyebut batas sampai 10⁹ untuk masing-masing bilangan; jumlah dua bilangan sebesar itu masih muat di int, tetapi begitu batasnya naik, ganti ke long long.

Soal 2 — Rata-rata tiga nilai

Soal. Baca tiga bilangan bulat, cetak rata-rata dengan dua desimal.

Cara berpikir. Jebakannya pembagian bulat. (80 + 85 + 91) / 3 memberi 85, bukan 85.33, karena semua bilangannya bulat. Bagi dengan 3.0 supaya C++ menghitung secara pecahan. Untuk mengatur jumlah desimal, pakai fixed dan setprecision(2) dari <iomanip>.

Pembahasan.

int a, b, c;
cin >> a >> b >> c;

double rata = (a + b + c) / 3.0;
cout << fixed << setprecision(2) << rata << "\n";

Uji.

Masukan Keluaran
7 8 9 8.00
1 1 2 1.33
-3 0 4 0.33

Baris kedua adalah pemeriksaan terpenting: kalau jawabanmu mencetak 1.00, berarti pembagiannya masih dikerjakan sebagai bilangan bulat.

Kesalahan umum.

  • Menulis / 3 lalu heran hasilnya tidak ada koma.
  • Memakai setprecision(2) tanpa fixed. Tanpa fixed, angka 2 berarti "dua digit berarti", sehingga 85.33 tercetak 85.

Soal 3 — Ubah detik

Soal. 3725 detik menjadi 1 2 5.

Cara berpikir. Satu jam 3600 detik, satu menit 60 detik. Pakai / untuk mengambil banyaknya satuan besar dan % untuk menyimpan sisanya:

  • jam = total / 3600
  • sisa setelah jam = total % 3600
  • menit = sisa / 60
  • detik = total % 60

Pembahasan.

int total;
cin >> total;

int jam = total / 3600;
int menit = (total % 3600) / 60;
int detik = total % 60;

cout << jam << " " << menit << " " << detik << "\n";

Uji.

Masukan Keluaran
3725 1 2 5
59 0 0 59
86399 23 59 59

Baris kedua menguji kasus kurang dari satu menit — jam dan menitnya harus tetap dicetak sebagai 0, bukan dilewati. Baris ketiga menguji nilai tepat satu detik sebelum satu hari penuh.

Kesalahan umum.

  • Menulis menit = total / 60 tanpa membuang jamnya lebih dulu; untuk 3725 hasilnya 62, bukan 2.
  • Lupa bahwa detik = total % 60 sudah benar tanpa perantara, karena % 60 tidak terpengaruh jam.

Soal 4 — Digit terakhir dan sisanya

Soal. 4071 menjadi 1 lalu 407.

Cara berpikir. Dua operasi yang akan kamu pakai terus menerus di soal teori bilangan: n % 10 mengambil digit terakhir, n / 10 membuangnya. Menggabungkan keduanya di dalam perulangan adalah cara memproses semua digit sebuah bilangan (Bab 05).

Pembahasan.

long long n;
cin >> n;

cout << n % 10 << "\n";
cout << n / 10 << "\n";

Uji.

Masukan Keluaran (dua baris)
4071 1 lalu 407
7 7 lalu 0
1000 0 lalu 100

Baris kedua adalah kasus tepi bilangan satu digit: membuang digit terakhirnya menyisakan 0, bukan kosong. Baris ketiga memastikan digit 0 tetap tercetak sebagai jawaban yang sah.

Kesalahan umum.

  • Mengubah bilangan menjadi string untuk mengambil karakter terakhir. Bisa, tetapi lebih panjang dan lebih mudah salah daripada % 10.
  • Memakai int untuk bilangan yang soalnya mengizinkan sampai 10¹⁸.

Tantangan — Deteksi luapan

Soal. Perbaiki perkalian yang hasilnya salah.

Cara berpikir. 2000000 * 1500000 bernilai 3 × 10¹², jauh di atas batas int (sekitar 2,1 × 10⁹). Hasilnya "berputar" dan mencetak angka yang tidak masuk akal tanpa pesan error apa pun.

Yang sering terlewat: mengubah variabel penampung saja tidak cukup.

int a = 2000000, b = 1500000;
long long hasil = a * b;     // masih salah

Perkaliannya tetap dihitung sebagai int dulu, baru hasil yang sudah rusak disalin ke long long. Perbaikan yang benar: jadikan operandnya long long.

Pembahasan.

long long a = 2000000;
long long b = 1500000;
long long hasil = a * b;

cout << hasil << "\n";       // 3000000000000

Kalau a dan b harus tetap int (misalnya karena dibaca dari masukan sebagai int), paksa satu sisinya:

long long hasil = (long long)a * b;

Uji. Program ini tidak membaca masukan. Sesudah diperbaiki, keluarannya:

3000000000000

Sebelum diperbaiki, angkanya berbeda dan bisa berbeda pula antar compiler — itu sendiri petunjuk bahwa yang terjadi adalah luapan, bukan salah hitung.

Kesalahan umum.

  • Mengira hasil yang salah berarti compiler rusak. Compiler tidak memeriksa luapan bilangan bulat; program tetap berjalan dengan angka yang salah.
  • Memakai double sebagai jalan pintas. double hanya menyimpan sekitar 15 digit berarti, sehingga untuk bilangan bulat besar hasilnya bisa meleset.

Latihan bergaya OSN — pembahasan

Berkas jawabannya ada di exercises/03/osn/solutions/.

Soal 1 — Ubin Lantai

Cara berpikir. Ubin tidak boleh dipotong, jadi banyaknya ubin ke arah panjang adalah P / S dengan pembagian bilangan bulat — pecahannya memang harus dibuang, karena ubin yang tidak utuh tidak dihitung. Hal yang sama berlaku untuk lebar. Luas sisanya adalah luas ruangan dikurangi luas yang tertutup ubin.

Sebelum menulis kode, periksa besarnya hasil: P dan L bisa 10⁹, jadi P × L bisa mencapai 10¹⁸. Itu melewati int dan masih muat di long long.

Pembahasan.

long long p, l, s;
cin >> p >> l >> s;

long long banyak = (p / s) * (l / s);
long long sisa = p * l - banyak * s * s;

cout << banyak << " " << sisa << "\n";

Mendeklarasikan ketiganya sebagai long long sejak awal menghilangkan kebutuhan cast di mana pun.

Uji.

Masukan Keluaran
10 7 3 6 16
5 5 5 1 0
1000000000 1000000000 7 20408163020408164 11999999964

Baris kedua adalah kasus tepi yang mudah terlewat: ruangan yang ukurannya pas membuat sisanya nol. Baris ketiga adalah pemeriksaan tipe — dengan int, keluarannya berubah menjadi angka negatif.

Kesalahan umum.

  • Menghitung (p * l) / (s * s). Hasilnya bukan jumlah ubin utuh: cara ini menghitung seolah-olah potongan sisa di dua tepi bisa disambung menjadi ubin baru.
  • Memakai int untuk p dan l, sehingga p * l meluap.

Soal 2 — Nilai Akhir

Cara berpikir. Jawabannya pecahan, jadi perhitungannya harus dilakukan dalam double. Menulis bobot sebagai 0.3 dan 0.4 sudah cukup: satu operand bertipe double membuat seluruh perkaliannya dikerjakan dalam double. Format keluarannya dikunci dengan fixed dan setprecision(2).

Pembahasan.

int tugas, uts, uas;
cin >> tugas >> uts >> uas;

double akhir = 0.3 * tugas + 0.3 * uts + 0.4 * uas;

cout << fixed << setprecision(2) << akhir << "\n";

fixed berarti "tulis dalam bentuk desimal biasa, bukan notasi ilmiah", dan setprecision(2) berarti "dua digit di belakang koma". Keduanya dari <iomanip> dan harus disertakan.

Uji.

Masukan Keluaran
80 75 90 82.50
100 100 100 100.00
0 0 1 0.40

Kesalahan umum.

  • Menulis 30 / 100 * tugas. 30 / 100 adalah pembagian dua bilangan bulat dan hasilnya 0, sehingga seluruh nilai akhir menjadi nol.
  • Lupa #include <iomanip>, sehingga compiler menolak setprecision.
  • Memakai setprecision(2) tanpa fixed. Tanpa fixed, angka 2 berarti dua digit berarti — 82.5 tercetak 82, dan 100 tercetak 1e+02.

Soal 3 — Deret Aritmetika

Cara berpikir. Dua rumus baku:

  • suku ke-n: a + (n - 1) × b
  • jumlah n suku: n × (2a + (n - 1) × b) / 2

Rumus kedua adalah rata-rata suku pertama dan suku terakhir dikalikan banyaknya suku. Pembagian dengan 2 selalu pas, karena n × (n − 1) pasti genap.

Periksa besarnya: (n − 1) × b bisa mencapai 10⁶ × 10⁶ = 10¹², dan n × (2a + (n − 1)b) bisa mencapai sekitar 10¹⁸. long long cukup, int tidak.

Pembahasan.

long long a, b, n;
cin >> a >> b >> n;

long long suku = a + (n - 1) * b;
long long jumlah = n * (2 * a + (n - 1) * b) / 2;

cout << suku << " " << jumlah << "\n";

Uji.

Masukan Keluaran
3 5 4 18 42
10 -3 5 -2 20
1000000 1000000 1000000 1000000000000 500000500000000000

Baris kedua memastikan beda negatif ikut tertangani; baris ketiga adalah pemeriksaan tipe.

Kesalahan umum.

  • Menulis a + n * b untuk suku ke-n. Suku pertama sudah a, jadi penambahannya n − 1 kali, bukan n kali.
  • Membagi dengan 2 terlalu awal: n / 2 * (2 * a + (n - 1) * b) salah untuk n ganjil, karena n / 2 membuang pecahannya lebih dulu.
  • Memakai double supaya "aman". Untuk jumlah sebesar 5 × 10¹⁷, double kehilangan digit terakhir dan mencetak angka yang hampir benar — yang tetap dinilai salah.

Kalau masih tersendat

  • Hasil pembagian selalu bulat → Bab 03, bagian Aritmetika.
  • Angka besar berubah negatif → Bab 03, bagian Kenapa luapan menghasilkan angka negatif.
  • Bingung cin membaca sampai mana → Bab 03, bagian Membaca masukan dengan cin.

Sebelum lanjut ke Bab 04, pastikan kamu bisa menjawab tanpa menjalankan program: berapa hasil 7 / 2, 7 % 2, dan (double)7 / 2, dan tipe apa yang dipakai untuk menampung jumlah 100.000 bilangan bernilai 10⁹.